安卓基礎開發——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 這幾個後續再開新文章去記錄學習過程。