天天看點

ReentrantLock詳解與實戰

ReentrantLock

在處理線程安全問題上,在JDK5以後引入了 Lock ,synchronized和Lock都可以保證線程安全問題!而Lock比synchronized使用更加靈活,也更适合複雜的并發場景。本文主要講解Lock的子類ReentrantLock。

一.ReentrantLock與synchronized 比較

(1)synchronized是獨占鎖,加鎖和解鎖的過程自動進行,易于操作,但不夠靈活。ReentrantLock也是獨占鎖,加鎖和解鎖的過程需要手動進行,不易操作,但非常靈活。

(2)synchronized可重入,因為加鎖和解鎖自動進行,不必擔心最後是否釋放鎖;ReentrantLock也可重入,但加鎖和解鎖需要手動進行,且次數需一樣,否則其他線程無法獲得鎖。

(3)synchronized不可響應中斷,一個線程擷取不到鎖就一直等着;ReentrantLock可以相應中斷。

ReentrantLock好像比synchronized關鍵字沒好太多,我們再去看看synchronized所沒有的,一個最主要的就是ReentrantLock還可以實作公平鎖機制。什麼叫公平鎖呢?也就是在鎖上等待時間最長的線程将獲得鎖的使用權。通俗的了解就是誰排隊時間最長誰先執行擷取鎖。

二.ReentrantLock 原理

這裡我們從源碼進行分析學習,分析過程:

1.ReentrantLock 類結構

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;
           

ReentrantLock 實作了Lock接口,實作了鎖的機制,Sync 源碼對他的描述是同步器提供所有實施機制,Sync 也是ReentrantLock的核心;

2.ReentrantLock 兩種初始化狀态

2.1 非公平鎖

public ReentrantLock() {
    sync = new NonfairSync();
}
           

2.2 公平鎖

public ReentrantLock(boolean fair) {
   sync = fair ? new FairSync() : new NonfairSync();
}
           

公平鎖與非公平鎖的最大差別就是公平鎖很盡可能的将等待時間最長的線程,具有最高的執行權重

無論是公平還是非公鎖的實作都是繼承了上面說到的 Sync,話題又回到了Sync;

3.Sync 設計

// 内部類,自定義同步器
   private static class Sync extends AbstractQueuedSynchronizer {
     // 是否處于占用狀态
     protected boolean isHeldExclusively() {
     }
     // 當狀态為0的時候擷取鎖
     public boolean tryAcquire(int acquires) {
     }
     // 釋放鎖,将狀态設定為0
     protected boolean tryRelease(int releases) { 
     }
    
   }
           

從源碼我們可以看出Sync繼承了AbstractQueuedSynchronizer,AbstractQueuedSynchronizer也是傳說的AQS,AQS的設計如下圖:

ReentrantLock詳解與實戰

在同步器中就包含了sync隊列。同步器擁有三個成員變量:sync隊列的頭結點head、sync隊列的尾節點tail和狀态state。對于鎖的擷取,請求形成節點,将其挂載在尾部,而鎖資源的轉移(釋放再擷取)是從頭部開始向後進行。對于同步器維護的狀态state,多個線程對其的擷取将會産生一個鍊式的結構。

4.ReentrantLock API

Modifier and Type Method and Description

int

getHoldCount()

查詢目前線程對此鎖的暫停數量。

protected Thread

getOwner()

傳回目前擁有此鎖的線程,如果不擁有,則傳回

null

protected Collection

getQueuedThreads()

傳回包含可能正在等待擷取此鎖的線程的集合。

int

getQueueLength()

傳回等待擷取此鎖的線程數的估計。

protected Collection

getWaitingThreads(Condition condition)

傳回包含可能在與此鎖相關聯的給定條件下等待的線程的集合。

int

getWaitQueueLength(Condition condition)

傳回與此鎖相關聯的給定條件等待的線程數的估計。

boolean

hasQueuedThread(Thread thread)

查詢給定線程是否等待擷取此鎖。

boolean

hasQueuedThreads()

查詢是否有線程正在等待擷取此鎖。

boolean

hasWaiters(Condition condition)

查詢任何線程是否等待與此鎖相關聯的給定條件。

boolean

isFair()

如果此鎖的公平設定為true,則傳回

true

boolean

isHeldByCurrentThread()

查詢此鎖是否由目前線程持有。

boolean

isLocked()

查詢此鎖是否由任何線程持有。

void

lock()

獲得鎖。

void

lockInterruptibly()

擷取鎖定,除非目前線程是

Condition

newCondition()

傳回[

Condition

]用于這種用途執行個體[

Lock

]執行個體。

String

toString()

傳回一個辨別此鎖的字元串以及其鎖定狀态。

boolean

tryLock()

隻有在調用時它不被另一個線程占用才能擷取鎖。

boolean

tryLock(long timeout, TimeUnit unit)

如果在給定的等待時間内沒有被另一個線程 [占用] ,并且目前線程尚未被 [保留,]則擷取該鎖。

void

unlock()

嘗試釋放此鎖。

三.實戰

這裡模拟售票,通過ReentrantLock的方式實作線程的安全

public class LockMain {
    public static void main(String[] args) {
        Window window = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}


/**
 * 售票視窗
 */
class Window implements Runnable{

    private volatile int num = 100;
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (num > 0){
                    System.out.println(Thread.currentThread().getName()+"視窗在售票,票号為"+ num);
                    num --;
                }else {
                    break;
                }
            }finally {
                lock.unlock();
            }

        }
    }
}
           

添加小編微信:372787553 ,帶你進入技術交流區,記得備注進群

繼續閱讀