安卓基礎開發——ReentrantLock的簡單使用

語言: CN / TW / HK

theme: scrolls-light

開啟掘金成長之旅!這是我參與「掘金日新計劃 · 2 月更文挑戰」的第 8 天,點擊查看活動詳情

前言

之前提到過有大佬給了建議,讓我創建一個共享的ReentrantLock來解決快速多次點擊檢查權限時候(當然情景是更為複雜,有其他UI操作同時進行(切換播放區域),應該還有一個進度條DIalog也在展示佔用屏幕)重複彈出彈窗。

但我對這個類不是很熟悉,之前只是看過介紹該知識點的文章,還沒開發中應用操作過,所以我們先看看ReentrantLock的簡單使用。

正篇

ReentrantLock概況

ReentrantLock(翻譯過來叫可重入鎖)是基於AQS進行的實現。

原理基礎

其中AQS的全稱為AbstractQueuedSynchronizer(直譯為抽象隊列同步器),是阻塞式鎖和同步器工具的框架,用一個state來表示鎖狀態,通過子類CAS(保證原子性)去操作,內部維護了一個等待隊列進行排隊,使用條件變量來實現等待、喚醒,支持多個條件變量功能,我們的ReentrantLock只是其中的一種實現方式,是AQS排他模式(獨佔鎖),此外還有使用AQS共享模式的ReentrantReadWriteLock中的讀鎖。

背景資料

在jdk1.6之前,我們使用鎖實現同步使用的是synchronized關鍵字,但是synchronized的實現原理是調用操作系統函數來實現加鎖/解鎖,而一旦涉及操作系統的函數,那麼代碼執行的效率就會變低,所以,使用synchronized關鍵字來實現加/解鎖就被稱為重量級鎖。\ 為了改善這一情況,Doug Lea就寫了ReentrantLock鎖,這種鎖分情況在jvm層面和操作系統層面完成加鎖/解鎖的過程,因而代碼執行效率顯著提高,後來Sun公司在jdk1.6以後也改進了synchronized,使得synchronized的執行效率和ReentrantLock差不多,甚至更好,但是由於ReentrantLock可以直接代碼操作加鎖/解鎖,可中斷獲取鎖等特性,因此使用的比較多。

ReentrantLock使用

當有多個線程需要對同一個共享變量進行操作的時候就需要考慮上鎖了,下面我們用一條樣例代碼説明:

線程1與線程2搶鎖的樣例代碼如下: ```Java package com.example.weekreference;

import java.util.concurrent.locks.ReentrantLock;

public class LockTest {

public static void main(String[] args){
    final ReentrantLock lock = new ReentrantLock(true);
    Thread LockTest1 = new Thread("LockTest1"){
        @Override
        public void run() {
            lock.lock();

            lockTest();
            lock.unlock();
        }
    };
    Thread LockTest2 = new Thread("LockTest2"){
        @Override
        public void run() {
            lock.lock();
            lockTest();
            lock.unlock();
        }
    };
    LockTest1.start();
    LockTest2.start();
}

/**
 * 打印線程名,以及線程休眠2秒後打印日誌
 */
public static void lockTest(){
    System.out.println(Thread.currentThread().getName());
    try {
        Thread.sleep(2000);
        System.out.println(" LockTest-------end");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

} ``` 我們可以右擊方法名旁邊的綠色三角形選擇覆蓋率測試運行(Run “...” with Coverage)

image.png 等待運行完成,我們能看到線程的打印

image.png 其中lock()方法就是加鎖,unlock()方法就是釋放鎖。

image.png

image.png 如果我們把上面樣例代碼的ReentrantLock對象的lock方法和unlock方法去除,再次運行打印,我們就發現:

image.png 線程1和線程2都會正常運行,但線程休眠方法打印的日誌會靠後,而加鎖和解鎖時,我們線程會依次等待,線程2會等線程1操作完釋放鎖後,才去運行。

而其中我們去實現ReentrantLock初始化時,我們傳入了一個boolean值,這是用來選擇是公平鎖還是非公平鎖的,默認是非公平鎖,有關公平鎖與非公平鎖(Java中是搶鎖時區分)簡要意義如下: - 公平鎖 - 按照請求鎖的順序來獲取鎖 - 非公平鎖 - 是否能獲取到鎖與請求鎖的順序無關

image.png 下面再介紹幾個常用的方法。

常用方法

  • isLocked() \ 是否持有鎖

  • isFair() \ 是否是公平鎖

  • hasQueuedThreads() \ 鎖等待隊列中是否有線程在等待鎖

  • hasQueuedThread(Thread thread) \ 指定的線程是否存在於鎖等待隊列中

  • getQueueLength() \ 鎖等待隊列中的等待線程數

  • isHeldByCurrentThread() \ 當前線程是否持有此鎖

  • getHoldCount() \ 當前線程持有此鎖的數量(是數量,而不是次數。比如嵌套鎖時,當前線程持有此鎖的數量就可能會大於 1)

總結

這個部分內容我自己實踐的也不夠,後續有新的關於ReentrantLock方面的會及時補充到這篇文章,主要本次未解釋自旋鎖和阻塞鎖,此外前言提到的共享狀態,我通過查詢資料得知AQS其實還有ReentrantReadWriteLock(讀寫鎖,讀鎖是共享鎖)和CountDownLatch(完全只用了AQS共享模式),本篇就講的是獨佔鎖(排他),而不是共享鎖(讀寫鎖): final ReentrantLock lock = new ReentrantLock(true); final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); final CountDownLatch countDownLatch = new CountDownLatch(1);

image.png 這幾個後續再開新文章去記錄學習過程。