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的設計如下圖:
在這裏插入圖片描述

在同步器中就包含了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 ,帶你進入技術交流區,記得備註進羣

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章