併發編程學習(4)Lock

初步認識JUC

Java.util.concurrent 是在併發編程中比較常用的工具類,裏面包含很多用來在併發場景中使用的組件。比如線程池、阻塞隊列、計時器、同步器、併發集合等等。併發包的作者是大名鼎鼎的 Doug Lea。

lock

在 Lock 接口出現之前,Java 中的應用程序對於多線程的併發安全處理只能基於synchronized 關鍵字來解決。但是 synchronized 在有些場景中會存在一些短板,也就是它並不適合於所有的併發場景。但是在 Java5 以後,Lock 的出現可以解決synchronized 在某些場景中的短板,它比 synchronized 更加靈活。
Lock 本質上是一個接口,它定義了釋放鎖和獲得鎖的抽象方法,定義成接口就意味着它定義了鎖的一個標準規範,也同時意味着鎖的不同實現。實現 Lock 接口的類有很多,以下爲幾個常見的鎖實現

Lock接口

void lock() // 如果鎖可用就獲得鎖,如果鎖不可用就阻塞直到鎖釋放
void lockInterruptibly() // 和lock()方法相似, 但阻塞的線程可中斷,拋 出java.lang.InterruptedException 異常
boolean tryLock() // 非阻塞獲取鎖;嘗試獲取鎖,如果成功返回 true
boolean tryLock(longtimeout, TimeUnit timeUnit)//帶有超時時間的獲取鎖方法
void unlock() // 釋放鎖

類圖

ReentrantLock

表示重入鎖,它是唯一一個實現了 Lock 接口的類。重入鎖指的是線程在獲得鎖之後,再次獲取該鎖不需要阻塞,而是直接關聯一次計數器增加重入次數,也就是如果當前線程 t1 通過調用 lock 方法獲取了鎖之後,再次調用 lock,是不會再阻塞去獲取鎖的,直接增加重試次數就行了。synchronized 和 ReentrantLock 都是可重入鎖。

重入鎖的設計目的

比如調用 demo 方法獲得了當前的對象鎖,然後在這個方法中再去調用demo2,demo2 中的存在同一個實例鎖,這個時候當前線程會因爲無法獲得demo2 的對象鎖而阻塞,就會產生死鎖。重入鎖的設計目的是避免線程的死鎖。

public class ReentrantDemo{
  public synchronized void demo(){// main獲得對象鎖
      System.out.println("begin:demo");
      demo2();
 }
 public void demo2(){
         System.out.println("begin:demo1");
         // 在次獲得對象鎖
         synchronized (this){
         }
 }
 public static void main(String[] args) {
       ReentrantDemo rd=new ReentrantDemo();
       new Thread(rd::demo).start();
       }
}

ReentrantReadWriteLock(讀寫鎖)

重入讀寫鎖,它實現了 ReadWriteLock 接口,在這個類中維護了兩個鎖,一個是 ReadLock,一個是 WriteLock,他們都分別實現了 Lock接口。讀寫鎖是一種適合讀多寫少的場景下解決線程安全問題的工具,基本原則是: 讀和讀不互斥、讀和寫互斥、寫和寫互斥。也就是說涉及到影響數據變化的操作都會存在互斥。

讀寫鎖的設計目的

我們以前理解的鎖,基本都是排他鎖,也就是這些鎖在同一時刻只允許一個線程進行訪問,而讀寫鎖在同一時刻可以允許多個線程訪問,但是在寫線程訪問時,所有的讀線程和其他寫線程都會被阻塞。讀寫鎖維護了一對鎖,一個讀鎖、一個寫鎖;一般情況下,讀寫鎖的性能都會比排它鎖好,因爲大多數場景讀是多於寫的。在讀多於寫的情況下,讀寫鎖能夠提供比排它鎖更好的併發性和吞吐量.

public class RWLock {
    static ReentrantReadWriteLock wrl=new ReentrantReadWriteLock();
    static Map<String,Object> cacheMap=new HashMap<>();
    static Lock read=wrl.readLock();
    static Lock write=wrl.writeLock();

    //線程B/C/D
    public static final Object get(String key){
        System.out.println("begin read data:"+key);
        read.lock(); //獲得讀鎖-> 阻塞
        try {
            return cacheMap.get(key);
        }finally {
            read.unlock();
        }
    }
    //線程A
    public static final Object put(String key,Object val){
        write.lock();//獲得了寫鎖
        try{
            return cacheMap.put(key,val);
        }finally {
            write.unlock();
        }
    }

    public static void main(String[] args) {
        wrl.readLock();//B線程 ->阻塞
        wrl.writeLock(); //A線程
        //讀->讀是可以共享
        //讀->寫 互斥
        //寫->寫 互斥
        //讀多寫少的場景
    }
}

在這個案例中,通過 hashmap 來模擬了一個內存緩存,然後使用讀寫所來保證這個內存緩存的線程安全性。當執行讀操作的時候,需要獲取讀鎖,在併發訪問的時候,讀鎖不會被阻塞,因爲讀操作不會影響執行結果。在執行寫操作是,線程必須要獲取寫鎖,當已經有線程持有寫鎖的情況下,當前線程會被阻塞,只有當寫鎖釋放以後,其他讀寫操作才能繼續執行。使用讀寫鎖提升讀操作的併發性,也保證每次寫操作對所有的讀寫操作的可見性

  • 讀鎖與讀鎖可以共享
  • 讀鎖與寫鎖不可以共享(排他)
  • 寫鎖與寫鎖不可以共享(排他)

StampedLock

stampedLock 是 JDK8 引入的新的鎖機制,可以簡單認爲是讀寫鎖的一個改進版本,讀寫鎖雖然通過分離讀和寫的功能使得讀和讀之間可以完全併發,但是讀和寫是有衝突的,如果大量的讀線程存在,可能會引起寫線程的飢餓。stampedLock 是一種樂觀的讀策略,使得樂觀鎖完全不會阻塞寫線程

ReentrantLock 的實現原理

AQS

在 Lock 中,用到了一個同步隊列 AQS,全稱 AbstractQueuedSynchronizer,它是一個同步工具也是 Lock 用來實現線程同步的核心組件獨佔鎖,每次只能有一個線程持有鎖,比如前面給大家演示的 ReentrantLock 就是以獨佔方式實現的互斥鎖共 享 鎖 , 允 許 多 個線程同時獲取鎖,併發訪問共享資源,比如ReentrantReadWriteLock
AQS 隊列內部維護的是一個 FIFO 的雙向鏈表,這種結構的特點是每個數據結構都有兩個指針,分別指向直接的後繼節點和直接前驅節點。所以雙向鏈表可以從任意一個節點開始很方便的訪問前驅和後繼。每個 Node 其實是由線程封裝,當線程爭搶鎖失敗後會封裝成 Node 加入到 ASQ 隊列中去;當獲取鎖的線程釋放鎖以後,會從隊列中喚醒一個阻塞的節點(線程)

Node 的組成

釋放鎖以及添加線程對於隊列的變化


裏會涉及到兩個變化

  • 新的線程封裝成 Node 節點追加到同步隊列中,設置 prev 節點以及修改當前節點的前置節點的 next 節點指向自己
  • 通過 CAS 講 tail 重新指向新的尾部節點

head 節點表示獲取鎖成功的節點,當頭結點在釋放同步狀態時,會喚醒後繼節點,如果後繼節點獲得鎖成功,會把自己設置爲頭結點,節點的變化過程如下

涉及到兩個變化

  • 修改head節點指向下一個獲得鎖的節點
  • 新的獲得鎖的節點,將prev的指針指向null

設置 head 節點不需要用 CAS,原因是設置 head 節點是由獲得鎖的線程來完成的,而同步鎖只能由一個線程獲得,所以不需要 CAS 保證,只需要把 head 節點設置爲原首節點的後繼節點,並且斷開原 head 節點的 next 引用即可

ReentrantLock 的源碼分析

ReentrantLock 默認是非公平鎖,因爲無論在什麼場景非公平鎖的效率都是會大於公平鎖的。
時序圖

public void lock(){
    sync.lock();
}

sync是一個靜態內部類,它繼承了AQS這個抽象類,前面說過AQS是一個同步工具,主要用來實現同步控制。我們在利用這個工具的時候,會繼承它來實現同步控制功能。 通過進一步分析,發現Sync這個類有兩個具體的實現,分別是 NofairSync(非公平鎖), FailSync(公平鎖).
公平鎖 表示所有線程嚴格按照FIFO來獲取鎖
非公平鎖 表示可以存在搶佔鎖的功能,也就是說不管當前隊列上是否存在其他線程等待,新線程都有機會搶佔鎖
NonfairSync.lock

  1. 非公平鎖和公平鎖最大的區別在於,在非公平鎖中我搶佔鎖的邏輯是,不管有沒有線程排隊,我先上來 cas 去搶佔一下
  2. CAS 成功,就表示成功獲得了鎖
  3. CAS 失敗,調用 acquire(1)走鎖競爭邏輯

非公平的AQS隊列頭結點的下一個節點不一定會獲得鎖,就是因爲在非公平鎖的釋放後,如果節點在cas的時候。另外一個線程正好進來cas鎖,如果這個節點沒他快,那麼鎖就會被這個插隊的獲得

final void lock() {
 if (compareAndSetState(0, 1))  //通過cas操作來修改state狀態,表示爭搶鎖的操作
 setExclusiveOwnerThread(Thread.currentThread());//設置當前獲得鎖狀態的線程
 else
 acquire(1);//嘗試去獲取鎖
}
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

通過 cas 樂觀鎖的方式來做比較並替換,這段代碼的意思是,如果當前內存中的state 的值和預期值 expect 相等,則替換爲 update。更新成功返回 true,否則返回 false.
這個操作是原子的,不會出現線程安全問題,這裏面涉及到Unsafe這個類的操作,以及涉及到 state 這個屬性的意義。state 是 AQS 中的一個屬性,它在不同的實現中所表達的含義不一樣,對於重入鎖的實現來說,表示一個同步狀態。它有兩個含義的表示

  1. 當 state=0 時,表示無鎖狀態
  2. 當 state>0 時,表示已經有線程獲得了鎖,也就是 state=1,但是因爲ReentrantLock 允許重入,所以同一個線程多次獲得同步鎖的時候,state 會遞增,比如重入 5 次,那麼 state=5。而在釋放鎖的時候,同樣需要釋放 5 次直到 state=0其他線程纔有資格獲得鎖

Unsafe 類
Unsafe 類是在 sun.misc 包下,不屬於 Java 標準。但是很多 Java 的基礎類庫,包括一些被廣泛使用的高性能開發庫都是基於 Unsafe 類開發的,比如 Netty、Hadoop、Kafka 等;Unsafe 可認爲是 Java 中留下的後門,提供了一些低層次操作,如直接內存訪問、線程的掛起和恢復、CAS、線程同步、內存屏障而 CAS 就是 Unsafe 類中提供的一個原子操作,第一個參數爲需要改變的對象,第二個爲偏移量(即之前求出來的 headOffset 的值),第三個參數爲期待的值,第四個爲更新後的值整個方法的作用是如果當前時刻的值等於預期值 var4 相等,則更新爲新的期望值 var5,如果更新成功,則返回 true,否則返回 false;

acquire(1)方法

  1. 通過 tryAcquire 嘗試獲取獨佔鎖,如果成功返回 true,失敗返回 false
  2. 如果 tryAcquire 失敗,則會通過 addWaiter 方法將當前線程封裝成 Node 添加到 AQS 隊列尾部
  3. acquireQueued,將 Node 作爲參數,通過自旋去嘗試獲取鎖。
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

NonfairSync.tryAcquire
這個方法的作用是嘗試獲取鎖,如果成功返回 true,不成功返回 false它是重寫 AQS 類中的 tryAcquire 方法,並且大家仔細看一下 AQS 中 tryAcquire方法的定義,並沒有實現,而是拋出異常。

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

nonfairTryAcquire

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread(); //獲取當前執行的線程
            int c = getState(); //獲得 state 的值
            if (c == 0) { //表示無鎖狀態
                if (compareAndSetState(0, acquires)) { //cas 替換 state 的值,cas 成功表示獲取鎖成功
                    setExclusiveOwnerThread(current); //保存當前獲得鎖的線程,下次再來的時候不要再嘗試競爭鎖
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { //如果同一個線程來獲得鎖,直接增加重入次數
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

addWaiter
當 tryAcquire 方法獲取鎖失敗以後,則會先調用 addWaiter 將當前線程封裝成Node.
入參 mode 表示當前節點的狀態,傳遞的參數是 Node.EXCLUSIVE,表示獨佔狀態。意味着重入鎖用到了 AQS 的獨佔鎖功能

  1. 將當前線程封裝成 Node
  2. 當前鏈表中的 tail 節點是否爲空,如果不爲空,則通過 cas 操作把當前線程的node 添加到 AQS 隊列
  3. 如果爲空或者 cas 失敗,調用 enq 將節點添加到 AQS 隊列
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail; //tail 是 AQS 中表示同比隊列隊尾的屬性,默認是 null
        if (pred != null) { //tail 不爲空的情況下,說明隊列中存在節點
            node.prev = pred; //把當前線程的 Node 的 prev 指向 tail
            if (compareAndSetTail(pred, node)) { //通過 cas 把 node加入到 AQS 隊列,也就是設置爲 tail
                pred.next = node; ;//設置成功以後,把原 tail 節點的 next指向當前 node
                return node;
            }
        }
        enq(node); ;//tail=null,把 node 添加到同步隊列
        return node;
    }

enq

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

圖解分析
假設 3 個線程來爭搶鎖,那麼截止到 enq 方法運行結束之後,或者調用 addwaiter方法結束後,AQS 中的鏈表結構圖

acquireQueued
通過 addWaiter 方法把線程添加到鏈表後,會接着把 Node 作爲參數傳遞給acquireQueued 方法,去競爭鎖

  1. 獲取當前節點的 prev 節點
  2. 如果 prev 節點爲 head 節點,那麼它就有資格去爭搶鎖,調用 tryAcquire 搶佔鎖
  3. 搶佔鎖成功以後,把獲得鎖的節點設置爲 head,並且移除原來的初始化 head節點
  4. 如果獲得鎖失敗,則根據 waitStatus 決定是否需要掛起線程
  5. 最後,通過 cancelAcquire 取消獲得鎖的操作
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor(); ;//獲取當前節點的 prev 節點
                if (p == head && tryAcquire(arg)) { //如果是 head 節點,說明有資格去爭搶鎖
                    setHead(node); //獲取鎖成功,也就是ThreadA 已經釋放了鎖,然後設置 head 爲 ThreadB 獲得執行權限
                    p.next = null; // help GC //把原 head 節點從鏈表中移除
                    failed = false;
                    return interrupted;
                }
                //ThreadA 可能還沒釋放鎖,使得 ThreadB 在執行 tryAcquire 時會返回 false
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;//並且返回當前線程在等待過程中有沒有中斷過。
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire 爭搶鎖失敗調用的方法
如果 ThreadA 的鎖還沒有釋放的情況下,ThreadB 和 ThreadC 來爭搶鎖肯定是會失敗,那麼失敗以後會調用shouldParkAfterFailedAcquire 方法
Node 有 5 中狀態,分別是:CANCELLED(1),SIGNAL(-1)、CONDITION(-2)、PROPAGATE(-3)、默認狀態(0)
CANCELLED: 在同步隊列中等待的線程等待超時或被中斷,需要從同步隊列中取消該 Node 的結點, 其結點的 waitStatus 爲 CANCELLED,即結束狀態,進入該狀態後的結點將不會再變化
SIGNAL: 只要前置節點釋放鎖,就會通知標識爲 SIGNAL 狀態的後續節點的線程
CONDITION: 和 Condition 有關係,後續會講解
PROPAGATE:共享模式下,PROPAGATE 狀態的線程處於可運行狀態
0:初始狀態

  1. 如果 ThreadA 的 pred 節點狀態爲 SIGNAL,那就表示可以放心掛起當前線程
  2. 通過循環掃描鏈表把 CANCELLED 狀態的節點移除
  3. 修改 pred 節點的狀態爲 SIGNAL,返回 false.
    返回 false 時,也就是不需要掛起,返回 true,則需要調用 parkAndCheckInterrupt掛起當前線程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus; //前置節點的waitStatus
        if (ws == Node.SIGNAL)//如果前置節點爲 SIGNAL,意味着只需要等待其他前置節點的線程被釋放
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true; //返回 true,意味着可以直接放心的掛起了
        if (ws > 0) { //ws 大於 0,意味着 prev 節點取消了排隊,直接移除這個節點就行
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev; //相當於: pred=pred.prev;node.prev=pred;
            } while (pred.waitStatus > 0); //這裏採用循環,從雙向列表中移除 CANCELLED 的節點
            pred.next = node;
        } else { //利用 cas 設置 prev 節點的狀態爲 SIGNAL(-1)

            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt 中斷
如果shouldParkAfterFailedAcquire返回了true,則會執行: parkAndCheckInterrupt()方法,它是通過LockSupport.park(this)將當前線程掛起到WATING狀態,它需要等待一箇中斷、unpark方法來喚醒它,通過這樣一種FIFO的機制的等待,來實現了Lock的操作。

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

LockSupport是Java6引入的一個類,提供了基本的線程同步原語。LockSupport實際上是調用了 Unsafe 類裏的函數,歸結到 Unsafe 裏,只有兩個函數–底層的就不分析了

    public native void unpark(Object var1);
    public native void park(boolean var1, long var2);

鎖的釋放流程

unlock

public final boolean release(int arg) {
   if (tryRelease(arg)) { //釋放鎖成功
      Node h = head; //得到 aqs 中 head 節點
      if (h != null && h.waitStatus != 0) //如果 head 節點不爲空並且狀態!=0.調用 unparkSuccessor(h)喚醒後續節點
         unparkSuccessor(h);
        return true;
   }
  return false;
}

tryRelease
這個方法可以認爲是一個設置鎖狀態的操作,通過將 state 狀態減掉傳入的參數值(參數是 1),如果結果狀態爲 0,就將排它鎖的 Owner 設置爲 null,以使得其它的線程有機會進行執行。
在排它鎖中,加鎖的時候狀態會增加 1(當然可以自己修改這個值),在解鎖的時候減掉 1,同一個鎖,在可以重入後,可能會被疊加爲 2、3、4 這些值,只有 unlock()的次數與 lock()的次數對應纔會將 Owner 線程設置爲空,而且也只有這種情況下才會返回 true

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

unparkSuccessor

    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus; //獲得 head 節點的狀態
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0); ;// 設置 head 節點狀態爲 0

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next; //得到 head 節點的下一個節點
        if (s == null || s.waitStatus > 0) {
        //如果下一個節點爲 null 或者 status>0 表示 cancelled 狀態.
            //通過從尾部節點開始掃描,找到距離 head 最近的一個waitStatus<=0 的節點
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null) //next 節點不爲空,直接喚醒這個線程即可
            LockSupport.unpark(s.thread);
    }

爲什麼在釋放鎖的時候是從 最後節點 tail 進行掃描

  1. 將新的節點的 prev 指向 tail
  2. 通過 cas 將 tail 設置爲新的節點,因爲 cas 是原子操作所以能夠保證線程安全性
  3. t.next=node;設置原 tail 的 next 節點指向新的節點

注意看else寫的邏輯

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }


在 cas 操作之後,t.next=node 操作之前。存在其他線程調用 unlock 方法從 head開始往後遍歷,由於 t.next=node 還沒執行意味着鏈表的關係還沒有建立完整。就會導致遍歷到 t 節點的時候被中斷。所以從後往前遍歷,一定不會存在這個問題。

圖解分析
通過鎖的釋放,原本的結構就發生了一些變化。head 節點的 waitStatus 變成了 0,ThreadB 被喚醒

然後通過unlock後 原來被掛起的線程是在 acquireQueued 方法中自選,所以被喚醒以後繼續從這個方法開始執行在acquireQueued方法中

  • 設置新 head 節點的 prev=null
  • 設置原 head 節點的 next 節點爲 null

公平鎖和非公平鎖的區別

鎖的公平性是相對於獲取鎖的順序而言的,如果是一個公平鎖,那麼鎖的獲取順序就應該符合請求的絕對時間順序,也就是 FIFO。 在上面分析的例子來說,只要CAS 設置同步狀態成功,則表示當前線程獲取了鎖,而公平鎖則不一樣,差異點有兩個

lock

final void lock() {
 acquire(1);
}
非公平鎖在獲取鎖的時候,會先通過 CAS 進行搶佔,而公平鎖則不會
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

tryAcquire

公平鎖
 protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

非公平鎖
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

不同的地方在於判斷條件多了hasQueuedPredecessors()方法,也就是加入了同步隊列中當前節點是否有前驅節點的判斷,如果該方法返回 true,則表示有線程比當前線程更早地請求獲取鎖,因此需要等待前驅線程獲取並釋放鎖之後才能繼續獲取鎖。

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