高併發學習之14鎖的源碼在梳理

1. 簡介

在前面的文章中我們介紹了lock接口AQS同步器,重入鎖,condition接口,這篇文章將準備將以上知識點在從源碼上梳理一遍。

2. Lock

在java5以後,增加了JUC的併發包且提供了Lock接口用來實現鎖的功能,它提供了與synchronized關鍵字類似的同步功能,只是在使用時需要顯式地獲取和釋放鎖。雖然它缺少了(通過synchronized塊或者方法所提供的)隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具備的同步特性。
Lock是一個接口,核心的兩個方法lock和unlock,下面是Lock接口源碼:

public interface Lock {
	//獲取鎖
    void lock();
	//獲取鎖的過程能夠響應中斷
    void lockInterruptibly() throws InterruptedException;
	//非阻塞式響應中斷能立即返回,獲取鎖放回true反之返回fasle
    boolean tryLock();
	//超時獲取鎖,在超時內或者未中斷的情況下能夠獲取鎖
    boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
	//釋放鎖
    void unlock();
	//獲取與lock綁定的等待通知組件,當前線程必須獲得了鎖才能進行等待,進行等待時會先釋放鎖,當再次獲取鎖時才能從等待中返回
    Condition newCondition();
}

3. AbstractQueuedSynchronizer(AQS抽象隊列同步器)

AQS是用來構建鎖或者其他同步組件的基礎框架,通過一個int變量來表示當前同步狀態,AQS依賴內部的同步隊列(一個FIFO雙向隊列)來完成同步狀態的管理,當前線程獲取同步狀態失時,AQS會將當前線程以及等待狀態等信息構造成爲一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試獲取同步狀態。
Node屬性如下:

static final class Node {
  int waitStatus; //表示節點的5種狀態
  Node prev; //前繼節點
  Node next; //後繼節點
  Node nextWaiter; //存儲在condition隊列中的後繼節點
  Thread thread; //當前線程
}

其中節點狀態:

  • Cancelled,值爲1,由於在同步隊列中等待的線程等待超時或者被中斷,需要從同步隊列中取消等待,節點進入該狀態將不會變化
  • Signal,值爲-1,後繼節點的線程處於等待狀態,而當前節點的線程如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的線程得以運行
  • Condition,值爲-2,節點在等待隊列中,節點線程等待在Condition 上,當其他線程對Condition 調用了 signal()方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到對同步狀態的獲取中
  • Propagate, 值爲-3, 表示下一次共享式同步狀態獲取將會無條件地被傳播下去
  • Initial, 值爲0,初始狀態

4. 從ReentrantLock重入鎖開始

ReentrantLock重入鎖是接口Lock 的一種實現,他支持重進入,除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇。下面是重入鎖的部分源碼:

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;
	
	//構造函數,默認是非公平鎖
	public ReentrantLock() {
        sync = new NonfairSync();
    }
    //構造函數,可以選擇是否公平鎖
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
	//非公平鎖
	static final class NonfairSync extends ReentrantLock.Sync{
		.....
	}
	//公平鎖
  	static final class FairSync extends ReentrantLock.Sync{
		......
	} 
	//繼承AQS(同步器)
    abstract static class Sync extends AbstractQueuedSynchronizer {
    	.....
    }
    //線程等待隊列
    public Condition newCondition() {
        return this.sync.newCondition();
    }

下面開始一個簡單例子:

	private static int count = 0;
    static Lock lock = new ReentrantLock();

    public static void inc() {
        lock.lock();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                RLDemo.inc();
            }).start();
        }
        Thread.sleep(3000);
        System.out.println("result:" + count);
    }

在上面的例子中,生成1000個線程,每個線程都調用inc方法實現count+1。這裏補充下,Lock 一般使用static修飾,作爲類的一個屬性。
在類加載的時候發現static修飾的lock 就會初始化new ReentrantLock()。重入鎖的構造函數,就知道他去生成了一個非公平的重入鎖實例,而重入鎖是通過AQS生成的,AQS是繼承AbstractOwnableSynchronizer抽象類。AbstractOwnableSynchronizer只是記錄當前被哪個線程持有。ReentrantLock初始化時序圖如下:
ReentrantLock初始化時序圖
ReentrantLock初始化完成後會有三個屬性:

  • Node head ,頭節點來源於AQS中Node
  • Node tail 尾點來源於AQS中Node
  • int state 同步狀態
  • Thread exclusiveOwnerThread 當前持有同步狀態的線程來源於AQS父類中AbstractOwnableSynchronizer

如圖:
ReentrantLock初始化完成屬性圖

lock初始完成後,將會調用其lock方法加鎖,注意lock()、unlock方法是對於鎖的使用者來說,而lock之後是對於鎖的開發者。

lock()加鎖時序圖如下:
lock加鎖時序圖
關於lock源碼大家可以到AQS同步器,重入鎖,看下具體代碼說明。

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