5.Java中的鎖的使用和實現(xiàn)介紹

Java并發(fā)編程的藝術(shù)筆記


目錄

  • Lock接口
  • 隊列同步器
  • 重入鎖
  • 讀寫鎖
  • LockSupport工具
  • Condition接口
  • 小結(jié)

Lock接口

Java SE 5之后强霎,并發(fā)包中新增了Lock接口(以及相關(guān)實現(xiàn)類)用來實現(xiàn)鎖功能堕义,它提供了與synchronized關(guān)鍵字類似的同步功能,只是在使用時需要 顯式 地獲取和釋放鎖脆栋。
雖然它缺少了(通過synchronized塊或者方法所提供的)隱式獲取釋放鎖的便捷性倦卖,但是卻擁有了鎖獲取與釋放的 可操作性可中斷的獲取鎖 以及 超時獲取鎖 等多種synchronized關(guān)鍵字所不具備的同步特性椿争。

使用synchronized關(guān)鍵字將會 隱式 地獲取鎖怕膛,但是它將鎖的獲取和釋放固化了,也就是先獲取再釋放秦踪。

Lock的使用:

Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
    lock.unlock();
}

注意 :

1.在finally塊中釋放鎖褐捻,目的是保證在獲取到鎖之后,最終能夠被釋放椅邓。
2.不要將獲取鎖的過程寫在try塊中柠逞,因為如果在獲取鎖(自定義鎖的實現(xiàn))時發(fā)生了異常,異常拋出的同時景馁,也會導(dǎo)致鎖無故釋放板壮。

Lock接口提供的synchronized關(guān)鍵字所不具備的主要特性:

  • 嘗試非阻塞性獲取鎖: 當(dāng)前線程嘗試獲取鎖,如果此時沒有其他線程占用此鎖合住,則成功獲取到鎖绰精。
  • 能被中斷的獲取鎖: 當(dāng)獲取到鎖的線程被中斷時,中斷異常會拋出并且會釋放鎖透葛。
  • 超時獲取鎖: 在指定時間內(nèi)獲取鎖笨使,如果超過時間還沒獲取,則返回僚害。

Lock 相關(guān)的API:

  • void lock();:獲取鎖硫椰,獲取之后返回
  • void lockInterruptibly() throws InterruptedException;:可中斷的獲取鎖
  • boolean tryLock();:嘗試非阻塞的獲取鎖
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException;: 超時獲取鎖。 超時時間結(jié)束萨蚕,未獲得鎖靶草,返回false.
  • void unlock();:釋放鎖
  • Condition newCondition();:獲取等待通知組件,改組件和鎖綁定门岔,當(dāng)前線程獲取到鎖才能調(diào)用wait()方法爱致,調(diào)用之后則會釋放鎖。

隊列同步器

隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器)寒随,是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架,它使用了一個int 成員變量表示同步狀態(tài),通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作妻往,并發(fā)包的作者Doug Lea期望它能夠成為實現(xiàn)大部分同步需求的基礎(chǔ)互艾。

同步器的主要使用方式是繼承AbstractQueuedSynchronizer,通過同步器提供的3個方法getState()讯泣、setState(int newState)compareAndSetState(int expect,int update)來進行線程安全的狀態(tài)同步纫普。

同步器是實現(xiàn)鎖的關(guān)鍵,在鎖的實現(xiàn)中聚合同步器好渠,利用同步器實現(xiàn)鎖的語義昨稼。
可以這樣理解二者之間的關(guān)系:

  • 鎖是面向使用者的,它定義了使用者與鎖交互的接口拳锚,隱藏了實現(xiàn)細(xì)節(jié)假栓;
  • 同步器面向的是鎖的實現(xiàn)者,它簡化了鎖的實現(xiàn)方式霍掺,屏蔽了同步狀態(tài)管理匾荆、線程的排隊、等待與喚醒等底層操作杆烁。
隊列同步器的接口與示例

AbstractQueuedSynchronizer可重寫的方法:

  • boolean tryAcquire(int arg):獨占式獲取同步狀態(tài)牙丽。
  • boolean tryRelease(int arg):獨占式釋放同步狀態(tài)。
  • int tryAcquireShared(int arg):共享式獲取同步狀態(tài)兔魂。
  • boolean tryReleaseShared(int arg):共享釋放取同步狀態(tài)烤芦。
  • boolean isHeldExclusively():當(dāng)前同步器是否在獨占式模式下被線程占用。

實現(xiàn)自定義同步組件時析校,將會調(diào)用同步器提供 獨占式獲取與釋放同步狀態(tài)拍棕、共享式獲取與釋放同步狀態(tài)查詢同步隊列中的等待線程情況 三類模板方法。

獨占鎖的示例代碼:

/**
 * @author https://github.com/103style
 * @date 2019/6/12 17:32
 */
public class TestLock implements Lock {
    private TestQueuedSync sync;
    /**
     * 獲取鎖
     */
    @Override
    public void lock() {
        sync.acquire(1);
    }
    /**
     * 可中斷的獲取鎖
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    /**
     * 嘗試非阻塞式獲取鎖
     */
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    /**
     * 嘗試非阻塞式獲取鎖
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquire(1);
    }
    /**
     * 釋放鎖
     */
    @Override
    public void unlock() {
        sync.release(1);
    }
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
    /**
     * 是否有同步隊列線程
     */
    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }
    /**
     * 鎖是否被占用
     */
    public boolean isLock() {
        return sync.isHeldExclusively();
    }
    private static class TestQueuedSync extends AbstractQueuedSynchronizer {
        /**
         * 獨占式獲取同步狀態(tài)
         */
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        /**
         * 獨占式釋放同步狀態(tài)
         */
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        /**
         * 同步狀態(tài)是否被占用
         */
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
        /**
         * 返回一個Condition勺良,每個condition都包含了一個condition隊列
         */
        Condition newCondition() {
            return new ConditionObject();
        }
    }
}

上述示例代碼中绰播,獨占鎖TestLock是一個自定義同步組件,它在同一時刻只允許一個線程占有鎖尚困。TestLock中定義了一個靜態(tài)內(nèi)部類TestQueuedSync繼承了同步器蠢箩,在tryAcquire(int acquires)方法中,如果經(jīng)過compareAndSetState設(shè)置成功事甜,則代表獲取了同步狀態(tài)1谬泌,而在tryRelease(int releases)方法中只是將同步狀態(tài)重置為0

用戶使用TestLock時并不會直接和內(nèi)部同步器的實現(xiàn)TestQueuedSync打交道逻谦,而是調(diào)用TestLock提供的方法掌实,在TestLock的實現(xiàn)中,以獲取鎖的lock()方法為例邦马,只需要在方法實現(xiàn)中調(diào)用同步器的模板方法acquire(int args)即可贱鼻,當(dāng)前線程調(diào)用該方法獲取同步狀態(tài)失敗后會被加入到同步隊列中等待宴卖,這樣就大大降低了實現(xiàn)一個可靠自定義同步組件的門檻。

隊列同步器的實現(xiàn)分析

接下來將從實現(xiàn)角度分析同步器是如何完成線程同步的:

  • 同步隊列 : 一個FIFO雙向隊列邻悬。
    當(dāng)前線程獲取同步狀態(tài)失敗時症昏,同步器會將當(dāng)前線程以及等待狀態(tài)等信息構(gòu)造成為一個節(jié)點Node并將其加入同步隊列,同時會阻塞當(dāng)前線程父丰,當(dāng)同步狀態(tài)釋放時肝谭,會把首節(jié)點中的線程喚醒,使其再次嘗試獲取同步狀態(tài)蛾扇。
    Node 保存 獲取同步狀態(tài)失敗的線程引用攘烛、等待狀態(tài) 以及 前驅(qū)和后繼節(jié)點節(jié)點的屬性類型名稱 以及 描述 如下:

    /**
     * 等待狀態(tài):
     *  CANCELLED : 1 在同步隊列中等待超時或被中斷镀首,需要從隊列中取消等待坟漱,在該狀態(tài)將不會變化
     *  SIGNAL : -1  后繼節(jié)點地線程處于等待狀態(tài),當(dāng)前節(jié)點釋放獲取取消同步狀態(tài)蘑斧,后繼節(jié)點地線程即開始運行
     *  CONDITION : -2  在等待隊列中靖秩,
     *  PROPAGATE : -3 下一次共享式同步狀態(tài)獲取將會無條件地被傳播下去
     *  INITAL : 0  初始狀態(tài)
     */
    volatile int waitStatus;
    volatile Node prev;//前驅(qū)節(jié)點
    volatile Node next;//后繼節(jié)點
    volatile Thread thread;//獲取同步狀態(tài)的線程
    Node nextWaiter;//等待隊列中的后繼節(jié)點。 如果節(jié)點是共享的的竖瘾,這個字段將是一個SHARED常量
    
    
同步隊列的基本結(jié)構(gòu)

如上圖所示沟突,同步器包含了兩個節(jié)點類型的引用,一個指向 頭節(jié)點捕传,而另一個指向 尾節(jié)點惠拭。
試想一下,當(dāng)一個線程成功地獲取了同步狀態(tài)(或者鎖)庸论,其他線程將無法獲取到同步狀態(tài)职辅,轉(zhuǎn)而被構(gòu)造成為節(jié)點并加入到同步隊列中,而這個加入隊列的過程必須要保證線程安全聂示,因此同步器提供了一個基于CAS的設(shè)置尾節(jié)點的方法:compareAndSetTail(Node expect,Node update)域携,它需要傳遞當(dāng)前線程“認(rèn)為”的尾節(jié)點和當(dāng)前節(jié)點,只有設(shè)置成功后鱼喉,當(dāng)前節(jié)點才正式與之前的尾節(jié)點建立關(guān)聯(lián)秀鞭。

  • 獨占式同步狀態(tài)獲取與釋放
    通過調(diào)用同步器的acquire(int arg)方法可以獲取同步狀態(tài),該方法對中斷不敏感扛禽,也就是由于線程獲取同步狀態(tài)失敗后進入同步隊列中锋边,后續(xù)對線程進行中斷操作時,線程不會從同步隊列中移出编曼。acquire(int arg)代碼如下:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    

    上述代碼主要完成了 同步狀態(tài)獲取豆巨、節(jié)點構(gòu)造加入同步隊列 以及 在同步隊列中自旋等待掐场。

    • 首先調(diào)用自定義同步器實現(xiàn)的tryAcquire(int arg)方法保證線程安全的獲取同步狀態(tài)
    • 如果獲取同步狀態(tài)失敗往扔,構(gòu)造同步節(jié)點(獨占式Node.EXCLUSIVE贩猎,同一時刻只能有一個線程成功獲取同步狀態(tài))并通過addWaiter(Node node)方法將該節(jié)點加入到同步隊列的尾部。
    • 最后調(diào)用acquireQueued(Node node,int arg)方法瓤球,使得該節(jié)點以“死循環(huán)”的方式獲取同步狀態(tài)
    • 如果獲取不到則阻塞節(jié)點中的線程融欧,而 被阻塞線程的喚醒 主要依靠 前驅(qū)節(jié)點的出隊阻塞線程被中斷 來實現(xiàn)葛假。

    我們來看下節(jié)點的構(gòu)造以及加入同步隊列的addWaiter(Node mode)initializeSyncQueue()方法:

    private Node addWaiter(Node mode) {
        Node node = new Node(mode);
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                U.putObject(node, Node.PREV, oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                initializeSyncQueue();
            }
        }
    }
    private final void initializeSyncQueue() {
        Node h;
        if (U.compareAndSwapObject(this, HEAD, null, (h = new Node())))
            tail = h;
    }
    
    

    上述代碼通過在“死循環(huán)”中使用compareAndSetTail(Node expect,Node update)方法來確保節(jié)點能夠被線程安全添加姆吭。 如果沒有尾節(jié)點的話宫患,則構(gòu)建一個新的同步隊列。

    接下來看下acquireQueued(final Node node, int arg)方法:

    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    
    

    acquireQueued(final Node node,int arg)方法中绿饵,當(dāng)前線程在“死循環(huán)”中嘗試獲取同步狀態(tài),而只有前驅(qū)節(jié)點是頭節(jié)點才能夠嘗試獲取同步狀態(tài)瓶颠,這是為什么拟赊?原因有兩個:

    • 頭節(jié)點是成功獲取到同步狀態(tài)的節(jié)點,而頭節(jié)點的線程釋放了同步狀態(tài)之后粹淋,將會喚醒其后繼節(jié)點吸祟,后繼節(jié)點的線程被喚醒后需要檢查自己的前驅(qū)節(jié)點是否是頭節(jié)點
    • 維護同步隊列的FIFO原則桃移。
      節(jié)點自旋獲取同步狀態(tài)

    由于非首節(jié)點線程前驅(qū)節(jié)點出隊或者被中斷而從等待狀態(tài)返回屋匕,隨后檢查自己的前驅(qū)是否是頭節(jié)點,如果是則嘗試獲取同步狀態(tài)借杰」牵可以看到節(jié)點和節(jié)點之間在循環(huán)檢查的過程中基本不相互通信,而是簡單地判斷自己的前驅(qū)是否為頭節(jié)點蔗衡,這樣就使得節(jié)點的釋放規(guī)則符合FIFO纤虽,并且也便于對過早通知的處理(過早通知是指前驅(qū)節(jié)點不是頭節(jié)點的線程由于中斷而被喚醒)。


    獨占式同步狀態(tài)獲取流程

    調(diào)用同步器的release(int arg)方法可以釋放同步狀態(tài)绞惦,然后會喚醒其后繼節(jié)點(進而使后繼節(jié)點重新嘗試獲取同步狀態(tài))逼纸。release(int arg)執(zhí)行之后會喚醒后繼的節(jié)點。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
    
    
  • 共享式同步狀態(tài)獲取與釋放
    共享式獲取獨占式獲取 最主要的區(qū)別在于 同一時刻能否有多個線程同時獲取到同步狀態(tài)济蝉。

    以文件的讀寫為例杰刽,寫操作 要求對資源的 獨占式訪問 ,而 讀操作 可以是 共享式訪問堆生。

    通過調(diào)用acquireShared(int arg)可以共享式地獲取同步狀態(tài)专缠,方法代碼如下:

    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    
    

    在共享式獲取的自旋過程中,成功獲取到同步狀態(tài)并退出自旋的條件就是tryAcquireShared(int arg)方法返回值 大于等于0淑仆。
    doAcquireShared(int arg)方法的自旋過程中涝婉,如果當(dāng)前節(jié)點的 前驅(qū)為頭節(jié)點 時,嘗試獲取同步狀態(tài)蔗怠,如果返回值 大于等于0墩弯,表示該次獲取同步狀態(tài)成功并從自旋過程中退出吩跋。

    共享式獲取也需要釋放同步狀態(tài),通過調(diào)用releaseShared(int arg)方法可以釋放同步狀態(tài):

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
    
    

    該方法在釋放同步狀態(tài)之后渔工,將會喚醒后續(xù)處于等待狀態(tài)的節(jié)點锌钮。對于能夠支持多個線程同時訪問的并發(fā)組件(比如Semaphore),它和獨占式主要區(qū)別在于tryReleaseShared(int arg)方法必須確保同步狀態(tài)(或者資源數(shù))線程安全釋放引矩,一般是通過循環(huán)和CAS來保證的梁丘,因為釋放同步狀態(tài)的操作會同時來自多個線程。

  • 超時獲取同步狀態(tài)
    通過調(diào)用同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超時獲取同步狀態(tài)旺韭,即在指定的時間段內(nèi)獲取同步狀態(tài)氛谜,如果獲取到同步狀態(tài)則返回true,否則区端,返回false值漫。該方法提供了傳統(tǒng)Java同步操作(比如synchronized關(guān)鍵字)所不具備的特性。

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L) {
                    cancelAcquire(node);
                    return false;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }
    
    

    該方法在自旋過程中织盼,當(dāng)節(jié)點的前驅(qū)節(jié)點為頭節(jié)點時嘗試獲取同步狀態(tài)杨何,如果獲取成功則從該方法返回,這個過程和獨占式同步獲取的過程類似沥邻。獲取失敗則會重新計算超時時間危虱。

    如果nanosTimeout小于等于spinForTimeoutThreshold1000納秒)時,將不會使該線程進行超時等待谋国,而是進入快速的自旋過程槽地。原因在于,非常短的超時等待無法做到十分精確芦瘾。

    獨占式超時獲取同步態(tài) 的流程下:

    獨占式超時獲取同步狀態(tài)的流程

  • 自定義同步組件
    設(shè)計一個同步工具:

    • 在同一時刻捌蚊,只允許至多兩個線程同時訪問,超過兩個線程的訪問將被阻塞近弟。
    • 能夠在同一時刻支持多個線程的訪問(共享式訪問)缅糟。

    代碼如下:省略了無關(guān)的重寫方法

    /**
     * @author xiaoke.luo@tcl.com 2019/6/14 11:15
     * 自定義同步組件
     * <p>
     * 實現(xiàn)以下功能
     * 1.在同一時刻,只允許至多兩個線程同時訪問祷愉,超過兩個線程的訪問將被阻塞窗宦。
     * 2.能夠在同一時刻支持多個線程的訪問(共享式訪問)。
     */
    public class CustomLock implements Lock {
        private CustomSyncQueue customSyncQueue = new CustomSyncQueue(2);
        public static void main(String[] args) {
            final Lock lock = new CustomLock();
            class Worker extends Thread {
                @Override
                public void run() {
                    while (true) {
                        lock.lock();
                        try {
                            SleepUtils.second(1);
                            System.out.println(Thread.currentThread().getName());
                            SleepUtils.second(1);
                        } finally {
                            lock.unlock();
                        }
                    }
                }
            }
            // 啟動10個線程
            for (int i = 0; i < 10; i++) {
                Worker w = new Worker();
                w.setDaemon(true);
                w.start();
            }
            // 每隔1秒換行
            for (int i = 0; i < 10; i++) {
                SleepUtils.second(1);
                System.out.println();
            }
        }
        @Override
        public void lock() {
            customSyncQueue.tryAcquireShared(1);
        }
        @Override
        public void unlock() {
            customSyncQueue.tryReleaseShared(1);
        }
        public static class CustomSyncQueue extends AbstractQueuedSynchronizer {
            public CustomSyncQueue(int count) {
                if (count <= 0) {
                    throw new IllegalStateException("count must >= 0");
                }
                setState(count);
            }
            @Override
            protected int tryAcquireShared(int reduceCount) {
                for (; ; ) {
                    int current = getState();
                    int newCount = current - reduceCount;
                    if (newCount < 0 || compareAndSetState(current, newCount)) {
                        return newCount;
                    }
                }
            }
            @Override
            protected boolean tryReleaseShared(int returnCount) {
                for (; ; ) {
                    int current = getState();
                    int newCount = current + returnCount;
                    if (compareAndSetState(current, newCount)) {
                        return true;
                    }
                }
            }
        }
    }
    
    

    上述代碼主要還是 CustomSyncQueuetryAcquireSharedtryReleaseShared 方法二鳄,當(dāng)tryAcquireShared(int reduceCount)方法返回值>=0時赴涵,當(dāng)前線程才獲取同步狀態(tài)。


重入鎖

重入鎖ReentrantLock订讼,就是支持重進入的鎖髓窜,它表示該鎖能夠支持 一個線程對資源的重復(fù)加鎖
除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇寄纵。

我們回顧下TestLocklock方法鳖敷,在 tryAcquire(int acquires)方法時沒有考慮占有鎖的線程再次獲取鎖的場景,而在調(diào)用tryAcquire(int acquires)方法時返回了false程拭,導(dǎo)致該線程被阻塞定踱。

在絕對時間上,先對鎖進行獲取的請求一定先被滿足恃鞋,那么這個鎖是公平的崖媚,反之,是不公平的山宾。
事實上至扰,公平的鎖機制往往沒有非公平的效率高鳍徽,但是资锰,并不是任何場景都是以TPS作為唯一的指標(biāo),公平鎖能夠減少“饑餓”發(fā)生的概率阶祭,等待越久的請求越是能夠得到優(yōu)先滿足绷杜。

下面我們來分析下ReentrantLock 的實現(xiàn):

  • 實現(xiàn)重進入
    重進入是指任意線程在獲取到鎖之后能夠再次獲取該鎖而不會被鎖所阻塞,該特性的實現(xiàn)需要解決以下兩個問題:

    • 線程再次獲取鎖
    • 鎖的最終釋放

    下面是ReentrantLock通過組合自定義同步器來實現(xiàn)鎖的獲取與釋放濒募,以非公平性(默認(rèn)的)實現(xiàn):

    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;
    }
    
    

    此方法通過判斷 當(dāng)前線程是否為獲取鎖的線程 來決定獲取操作是否成功鞭盟,如果是獲取鎖的線程再次請求,則將同步狀態(tài)值進行增加并返回true瑰剃,表示獲取同步狀態(tài)成功齿诉。

    下面看釋放鎖的方法tryRelease(int releases)

    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;
    }
    
    

    通過檢查 state == 0 來判斷是否需要繼續(xù)釋放鎖。

  • 公平與非公平獲取鎖的區(qū)別
    公平性與否是針對獲取鎖而言的晌姚,如果一個鎖是公平的粤剧,那么鎖的獲取順序就應(yīng)該符合請求的絕對時間順序,也就是 FIFO挥唠。
    對于上面介紹的非公平鎖實現(xiàn)的nonfairTryAcquire(int acquires)抵恋,只要 CAS 設(shè)置同步狀態(tài)成功,即獲取到鎖宝磨,而公平鎖則不同弧关,如下:

    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;
    }
    
    

    相比非公平鎖的實現(xiàn),公平鎖的實現(xiàn)在獲取鎖的時候多了一個!hasQueuedPredecessors()判斷:

    public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    
    

    即加入了 同步隊列中當(dāng)前節(jié)點是否有前驅(qū)節(jié)點的判斷 唤锉,如果該方法返回 true世囊,則表示有線程比當(dāng)前線程更早地請求獲取鎖,因此 需要等待前驅(qū)線程獲取并釋放鎖之后才能繼續(xù)獲取鎖窿祥。


讀寫鎖

之前提到鎖(如TestLockReentrantLock)基本都是排他鎖株憾,這些鎖在同一時刻只允許一個線程進行訪問,而讀寫鎖在同一時刻可以允許多個讀線程訪問壁肋,但是在寫線程訪問時号胚,所有的讀線程和其他寫線程均被阻塞籽慢。
讀寫鎖維護了一對鎖,一個 讀鎖 和一個 寫鎖猫胁,通過分離讀鎖和寫鎖箱亿,使得并發(fā)性相比一般的排他鎖有了很大提升。

一般情況下弃秆,讀寫鎖 的性能都會比 排它鎖 好届惋,因為大多數(shù)場景 讀是多于寫 的。在讀多于寫的情況下菠赚,讀寫鎖 能夠提供比 排它鎖 更好的 并發(fā)性吞吐量脑豹。Java并發(fā)包提供讀寫鎖的實現(xiàn)是ReentrantReadWriteLock ,特性如下:

  • 公平性選擇 :支持公平和非公平的方式獲取鎖衡查,吞吐量非公平優(yōu)于公平瘩欺。
  • 重進入 : 讀鎖在獲取鎖之后再獲取讀鎖,寫鎖在獲取鎖之后再獲取讀鎖和寫鎖拌牲。
  • 鎖降級 :遵循獲取寫鎖俱饿、獲取讀鎖在釋放寫鎖的次序,寫鎖能夠降級為讀鎖塌忽。
讀寫鎖的接口與示例

ReadWriteLock僅定義了獲取讀鎖和寫鎖的兩個方法拍埠,即readLock()方法和writeLock()方法,而其實現(xiàn)類ReentrantReadWriteLock土居,除了接口方法之外枣购,還提供了一些便于外界監(jiān)控其內(nèi)部工作狀態(tài)的方法,這些方法如下:

  • getReadLockCount():返回當(dāng)前讀鎖獲取的次數(shù)
  • getReadHoldCount():返回當(dāng)前線程獲取讀鎖的次數(shù)
  • isWriteLocked():判斷寫鎖是否被獲取
  • getWriteHoldCount():返回當(dāng)前寫鎖被獲取的次數(shù)

以下是讀寫鎖的使用示例代碼:
通過讀寫鎖保證 非線程安全的HashMap的讀寫是線程安全的擦耀。

static Map<String, Object> map = new HashMap<>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
/**
 * 獲取一個key對應(yīng)的value
 */
public static final Object get(String key) {
    r.lock();
    try {
        return map.get(key);
    } finally {
        r.unlock();
    }
}
/**
 * 設(shè)置key對應(yīng)的value棉圈,并返回舊的value
 */
public static final Object put(String key, Object value) {
    w.lock();
    try {
        return map.put(key, value);
    } finally {
        w.unlock();
    }
}
/**
 * 清空所有的內(nèi)容
 */
public static final void clear() {
    w.lock();
    try {
        map.clear();
    } finally {
        w.unlock();
    }
}

讀寫鎖的實現(xiàn)分析

主要包括:讀寫狀態(tài)的設(shè)計、寫鎖的獲取與釋放埂奈、讀鎖的獲取與釋放以及鎖降級迄损。

  • 讀寫狀態(tài)的設(shè)計
    讀寫鎖將變量切分成了兩個部分,高16位表示讀账磺,低16位表示寫芹敌,如下圖:

    讀寫鎖狀態(tài)的劃分方式

    當(dāng)前同步狀態(tài)表示一個線程已經(jīng)獲取了寫鎖,且重進入了兩次垮抗,同時也連續(xù)獲取了兩次讀鎖氏捞。
    讀寫鎖是如何迅速確定讀和寫各自的狀態(tài)呢?答案是通過位運算冒版。
    假設(shè)當(dāng)前同步狀態(tài)值為S液茎,寫狀態(tài)等于S&0x0000FFFF(將高16位全部抹去),讀狀態(tài)等于S>>16(無符號補0右移16位)。
    當(dāng)寫狀態(tài)增加1時捆等,等于S+1滞造,當(dāng)讀狀態(tài)增加1時,等于S+(1<<16)栋烤,也就是S+0x00010000谒养。
    根據(jù)狀態(tài)的劃分能得出一個推論:S != 0時,當(dāng)寫狀態(tài)S&0x0000FFFF = 0時明郭,則讀狀態(tài)S>>16 > 0买窟,即讀鎖已被獲取。

  • 寫鎖的獲取與釋放
    寫鎖是一個支持重進入的排它鎖薯定。如果當(dāng)前線程已經(jīng)獲取了寫鎖始绍,則增加寫狀態(tài)。如果當(dāng)前線程在獲取寫鎖時话侄,讀鎖已經(jīng)被獲瓤魍啤(讀狀態(tài)不為0)或者該線程不是已經(jīng)獲取寫鎖的線程,則當(dāng)前線程進入等待狀態(tài)满葛。
    ReentrantReadWriteLoctryAcquire方法如下:

    protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        int c = getState();
        int w = exclusiveCount(c);
        if (c != 0) {
            // (Note: if c != 0 and w == 0 then shared count != 0)
            // 存在讀鎖或者當(dāng)前獲取線程不是已經(jīng)獲取寫鎖的線程
            if (w == 0 || current != getExclusiveOwnerThread())
                return false;
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            // Reentrant acquire
            setState(c + acquires);
            return true;
        }
        if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
            return false;
        setExclusiveOwnerThread(current);
        return true;
    }
    
    

    該方法除了重入條件(當(dāng)前線程為獲取了寫鎖的線程)之外径簿,增加了一個 讀鎖是否存在 的判斷。如果存在讀鎖嘀韧,則寫鎖不能被獲取。

    寫鎖的釋放與ReentrantLock的釋放過程基本類似缠捌,每次釋放均減少寫狀態(tài)锄贷,當(dāng)寫狀態(tài)為0時表示寫鎖已被釋放,從而等待的讀寫線程能夠繼續(xù)訪問讀寫鎖曼月,同時前次寫線程的修改對后續(xù)讀寫線程可見谊却。

  • 讀鎖的獲取與釋放
    讀鎖是一個支持重進入的 共享鎖,它能夠被多個線程同時獲取哑芹,在沒有其他寫線程訪問(或者寫狀態(tài)為0)時炎辨,讀鎖總會被成功地獲取,而所做的也只是(線程安全的)增加讀狀態(tài)聪姿。

    如果當(dāng)前線程已經(jīng)獲取了讀鎖碴萧,則增加讀狀態(tài)。如果當(dāng)前線程在獲取讀鎖時末购,寫鎖已被其他線程獲取破喻,則進入等待狀態(tài)。

    ReentrantReadWriteLocktryAcquireShared方法:

    protected final int tryAcquireShared(int unused) {
        Thread current = Thread.currentThread();
        int c = getState();
        if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
            return -1;
        int r = sharedCount(c);
        if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
            ...
            return 1;
        }
        return fullTryAcquireShared(current);
    }
    final int fullTryAcquireShared(Thread current) {
        HoldCounter rh = null;
        for (;;) {
            int c = getState();
            if (exclusiveCount(c) != 0) {
                if (getExclusiveOwnerThread() != current)
                    return -1;
                // else we hold the exclusive lock; blocking here
                // would cause deadlock.
            } else if (readerShouldBlock()) {
                // Make sure we're not acquiring read lock reentrantly
                if (firstReader == current) {
                    // assert firstReaderHoldCount > 0;
                } else {
                    if (rh == null) {
                        rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current)) {
                            rh = readHolds.get();
                            if (rh.count == 0)
                                readHolds.remove();
                        }
                    }
                    if (rh.count == 0)
                        return -1;
                }
            }
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            if (compareAndSetState(c, c + SHARED_UNIT)) {
                if (sharedCount(c) == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    if (rh == null)
                        rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                    cachedHoldCounter = rh; // cache for release
                }
                return 1;
            }
        }
    }
    
    

    如果其他線程已經(jīng)獲取了寫鎖盟榴,則當(dāng)前線程獲取讀鎖失敗曹质,進入等待狀態(tài)。
    如果當(dāng)前線程獲取了寫鎖或者寫鎖未被獲取,則當(dāng)前線程增加讀狀態(tài)羽德,成功獲取讀鎖几莽。

    讀鎖的每次釋放均減少讀狀態(tài),減少的值是1<<16宅静。

  • 鎖降級
    鎖降級指的是 寫鎖降級成為讀鎖银觅。
    如果當(dāng)前線程擁有寫鎖,然后將其釋放坏为,最后再獲取讀鎖究驴,這種分段完成的過程不能稱之為鎖降級。鎖降級是指把持自确(當(dāng)前擁有的)寫鎖洒忧,再獲取到讀鎖,隨后釋放(先前擁有的)寫鎖的過程够颠。
    以下是是鎖降級的示例:

    //當(dāng)數(shù)據(jù)發(fā)生變更后熙侍,update變量(布爾類型且volatile修飾)被設(shè)置為false
    public void processData() {
        readLock.lock();
        if (!update) {
            // 必須先釋放讀鎖
            readLock.unlock();
            // 鎖降級從寫鎖獲取到開始
            writeLock.lock();
            try {
                if (!update) {
                    // 準(zhǔn)備數(shù)據(jù)的流程(略)
                    update = true;
                }
                readLock.lock();
            } finally {
                writeLock.unlock();
            }
            // 鎖降級完成,寫鎖降級為讀鎖
        }
        try {
            // 使用數(shù)據(jù)的流程(略)
        } finally {
            readLock.unlock();
        }
    }
    
    

    上述示例中履磨,當(dāng)數(shù)據(jù)發(fā)生變更后蛉抓,布爾類型且volatile修飾update變量被設(shè)置為false,此時所有訪問processData()方法的線程都能夠感知到變化剃诅,但只有一個線程能夠獲取到寫鎖巷送,其他線程會被阻塞在讀鎖和寫鎖的lock()方法上。當(dāng)前線程獲取寫鎖完成數(shù)據(jù)準(zhǔn)備之后矛辕,再獲取讀鎖笑跛,隨后釋放寫鎖,完成鎖降級聊品。

    鎖降級中讀鎖的獲取是否必要呢飞蹂?
    答案是必要的。主要是為了 保證數(shù)據(jù)的可見性翻屈。
    如果當(dāng)前線程不獲取讀鎖而是直接釋放寫鎖陈哑,假設(shè)此刻另一個線程(記作線程T)獲取了寫鎖并修改了數(shù)據(jù),那么 當(dāng)前線程無法感知線程T的數(shù)據(jù)更新伸眶。
    如果當(dāng)前線程獲取讀鎖惊窖,即遵循鎖降級的步驟,則線程T將會被阻塞赚抡,直到當(dāng)前線程使用數(shù)據(jù)并釋放讀鎖之后爬坑,線程T才能獲取寫鎖進行數(shù)據(jù)更新。

    RentrantReadWriteLock不支持鎖升級涂臣。目的也是保證數(shù)據(jù)可見性盾计,如果讀鎖已被多個線程獲取售担,其中任意線程成功獲取了寫鎖并更新了數(shù)據(jù),則其更新對其他獲取到讀鎖的線程是不可見的署辉。


LockSupport工具

當(dāng)需要阻塞或喚醒一個線程的時候族铆,都會使用LockSupport工具類來完成相應(yīng)工作。
LockSupport定義了一組的公共靜態(tài)方法哭尝,這些方法提供了最基本的 線程阻塞和喚醒功能哥攘,而LockSupport也成為構(gòu)建同步組件的基礎(chǔ)工具。

LockSupport提供的 阻塞和喚醒的方法 如下:

  • park():阻塞當(dāng)前線程材鹦,只有調(diào)用 unpark(Thread thread)或者被中斷之后才能從park()返回逝淹。
  • parkNanos(long nanos):再park()的基礎(chǔ)上增加了超時返回。
  • parkUntil(long deadline):阻塞線程知道 deadline 對應(yīng)的時間點桶唐。
  • park(Object blocker)Java 6時增加栅葡,blocker為當(dāng)前線程在等待的對象。
  • parkNanos(Object blocker, long nanos)Java 6時增加尤泽,blocker為當(dāng)前線程在等待的對象欣簇。
  • parkUntil(Object blocker, long deadline)Java 6時增加,blocker為當(dāng)前線程在等待的對象坯约。
  • unpark(Thread thread):喚醒處于阻塞狀態(tài)的線程 thread熊咽。

有對象參數(shù)的阻塞方法在線程dump時,會有更多的現(xiàn)場信息闹丐。


Condition接口

任意一個Java對象横殴,都擁有一組監(jiān)視器方法,定義在java.lang.Object)妇智,主要包括wait()滥玷、wait(long timeout)notify()以及notifyAll()方法巍棱,這些方法與synchronized同步關(guān)鍵字配合,可以實現(xiàn)等待/通知模式蛋欣。

Condition接口也提供了類似Object的監(jiān)視器方法航徙,與Lock配合可以實現(xiàn) 等待/通知 模式,但是這兩者在使用方式以及功能特性上還是有差別的陷虎。

以下是Object的監(jiān)視器方法與Condition接口的對比:

對比項 Object Condition
前置條件 獲取對象的鎖 調(diào)用Lock.lock()獲取鎖到踏;調(diào)用Lock.newCondition()獲取condition對象
調(diào)用方式 object.wait() condition.wait()
等待隊列個數(shù) 一個 多個
當(dāng)前線程釋放鎖并進入等待狀態(tài) 支持 支持
當(dāng)前線程釋放鎖并進入等待狀態(tài),在等待狀態(tài)中不響應(yīng)中斷 不支持 支持
當(dāng)前線程釋放鎖并進入超時等待狀態(tài) 支持 支持
當(dāng)前線程釋放鎖并進入等待狀態(tài)到將來某時間 不支持 支持
喚醒等待隊列的一個線程 支持 支持
喚醒等待隊列的全部線程 支持 支持
Condition接口與示例

Condition定義了等待/通知兩種類型的方法尚猿,當(dāng)前線程調(diào)用這些方法時窝稿,需要提前獲取到Condition對象關(guān)聯(lián)的鎖。
Condition對象是由調(diào)用Lock對象的newCondition()方法創(chuàng)建出來的凿掂,換句話說伴榔,Condition是依賴Lock對象的纹蝴。

Condition的使用方式比較簡單,需要注意在調(diào)用方法前獲取鎖踪少,如下:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException {
    lock.lock();
    try {
        condition.await();
    } finally {
        lock.unlock();
    }
}
public void conditionSignal() throws InterruptedException {
    lock.lock();
    try {
        condition.signal();
    } finally {
        lock.unlock();
    }
}

Condition 接口方法介紹:

  • void await() throws InterruptedException : 當(dāng)前線程進入等待狀態(tài)直到被通知或中斷
  • void awaitUninterruptibly() :當(dāng)前線程進入等待狀態(tài)直到被通知塘安,對中斷不敏感
  • long awaitNanos(long var1) throws InterruptedException :當(dāng)前線程進入等待狀態(tài)直到被通知、中斷或超時
  • boolean await(long var1, TimeUnit var3) throws InterruptedException :當(dāng)前線程進入等待狀態(tài)直到被通知援奢、中斷或超時
  • boolean awaitUntil(Date var1) throws InterruptedException :當(dāng)前線程進入等待狀態(tài)直到被通知兼犯、中斷或到某一時間
  • void signal() :喚醒Condition上一個在等待的線程
  • void signalAll() :喚醒Condition上全部在等待的線程

獲取一個Condition必須通過Lock的newCondition()方法。

通過下面這個有界隊列的示例我們來深入了解下 Condition 的使用方式:

public class BoundedQueue<T> {
    private Object[] items;
    // 添加的下標(biāo)集漾,刪除的下標(biāo)和數(shù)組當(dāng)前數(shù)量
    private int addIndex, removeIndex, count;
    private Lock lock = new ReentrantLock();
    private Condition notEmpty = lock.newCondition();
    private Condition notFull = lock.newCondition();

    public BoundedQueue(int size) {
        items = new Object[size];
    }

    // 添加一個元素切黔,如果數(shù)組滿,則添加線程進入等待狀態(tài)具篇,直到有"空位"
    public void add(T t) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length){
                notFull.await();
            }
            items[addIndex] = t;
            if (++addIndex == items.length)
                addIndex = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    // 由頭部刪除一個元素纬霞,如果數(shù)組空,則刪除線程進入等待狀態(tài)栽连,直到有新添加元素
    @SuppressWarnings("unchecked")
    public T remove() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await();
            Object x = items[removeIndex];
            if (++removeIndex == items.length)
                removeIndex = 0;
            --count;
            notFull.signal();
            return (T) x;
        } finally {
            lock.unlock();
        }
    }
}

上述代碼addremove 方法 都需要先獲取鎖保證數(shù)據(jù)的可見性和排它性险领。
當(dāng)儲存數(shù)組滿了的時候時候調(diào)用notFull.await(),線程即釋放鎖并進入等待隊列秒紧。
當(dāng)儲存數(shù)組未滿時绢陌,則添加到數(shù)組,并通知 notEmpty 中等待的線程熔恢。
方法中使用while循環(huán)是為了防止過早或者意外的通知脐湾。

Condition的實現(xiàn)分析

主要包括 等待隊列、等待和通知叙淌。

  • 等待隊列
    等待隊列是一個FIFO的隊列秤掌,在隊列中的每個節(jié)點都包含了一個線程引用,該線程就是在Condition對象上等待的線程鹰霍,如果一個線程調(diào)用了Condition.await()方法闻鉴,那么該線程將會釋放鎖、構(gòu)造成節(jié)點加入等待隊列并進入等待狀態(tài)茂洒。同步隊列和等待隊列中節(jié)點類型都是同步器的靜態(tài)內(nèi)部類AbstractQueuedSynchronizer.Node孟岛。

    線程調(diào)用Condition.await(),即以當(dāng)前線程構(gòu)造節(jié)點督勺,并加入等待隊列的尾部渠羞。

    等待隊列的基本結(jié)構(gòu)

    如下圖所示,Condition的實現(xiàn)是同步器的內(nèi)部類智哀,因此每個Condition實例都能夠訪問同步器提供的方法次询,相當(dāng)于每個Condition都擁有所屬同步器的引用。

    同步隊列與等待隊列

  • 等待
    調(diào)用Conditionawait()等方法瓷叫,會使當(dāng)前線程進入等待隊列并釋放鎖屯吊,同時線程狀態(tài)變?yōu)榈却隣顟B(tài)送巡。當(dāng)從await()方法返回時,當(dāng)前線程一定獲取了Condition相關(guān)聯(lián)的鎖雌芽。

    Conditionawait()源碼:

    public final void await() throws InterruptedException {
        if (Thread.interrupted()) {
            throw new InterruptedException();
        } else {
            AbstractQueuedSynchronizer.Node node = this.addConditionWaiter();
            int savedState = AbstractQueuedSynchronizer.this.fullyRelease(node);
            int interruptMode = 0;
            while(!AbstractQueuedSynchronizer.this.isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = this.checkInterruptWhileWaiting(node)) != 0) {
                    break;
                }
            }
            if (AbstractQueuedSynchronizer.this.acquireQueued(node, savedState) && interruptMode != -1) {
                interruptMode = 1;
            }
            if (node.nextWaiter != null) {
                this.unlinkCancelledWaiters();
            }
            if (interruptMode != 0) {
                this.reportInterruptAfterWait(interruptMode);
            }
        }
    }
    
    

    調(diào)用該方法的線程成功獲取了鎖的線程授艰,也就是同步隊列中的首節(jié)點,該方法會將當(dāng)前線程構(gòu)造成節(jié)點并加入等待隊列中世落,然后釋放同步狀態(tài)淮腾,喚醒同步隊列中的后繼節(jié)點,然后當(dāng)前線程會進入等待狀態(tài)屉佳。
    當(dāng)?shù)却犃兄械墓?jié)點被喚醒谷朝,則喚醒節(jié)點的線程開始嘗試獲取同步狀態(tài)。如果不是通過其他線程調(diào)用Condition.signal()方法喚醒武花,而是對等待線程進行中斷圆凰,則會拋出InterruptedException

    當(dāng)前線程加入等待隊列

  • 通知
    調(diào)用Conditionsignal()方法体箕,將會喚醒在等待隊列中等待時間最長的節(jié)點(首節(jié)點)专钉,在喚醒節(jié)點之前,會將節(jié)點移到同步隊列中累铅。

    Conditionsignal()源碼:

    public final void signal() {
        if (!AbstractQueuedSynchronizer.this.isHeldExclusively()) {
            throw new IllegalMonitorStateException();
        } else {
            AbstractQueuedSynchronizer.Node first = this.firstWaiter;
            if (first != null) {
                this.doSignal(first);
            }
        }
    }
    private void doSignal(AbstractQueuedSynchronizer.Node first) {
        do {
            if ((this.firstWaiter = first.nextWaiter) == null) {
                this.lastWaiter = null;
            }
            var1.nextWaiter = null;
        } while (!AbstractQueuedSynchronizer.this.transferForSignal(first) && (first = this.firstWaiter) != null);
    
    }
    
    

    調(diào)用該方法的前置條件是當(dāng)前線程必須獲取了鎖跃须,可以看到signal()方法進行了isHeldExclusively()檢查,也就是當(dāng)前線程必須是獲取了鎖的線程娃兽。
    接著獲取等待隊列的首節(jié)點菇民,將其移動到同步隊列并使用LockSupport喚醒節(jié)點中的線程。

    節(jié)點從等待隊列移動到同步隊列

    通過調(diào)用同步器的enq(Node node)方法投储,等待隊列中的頭節(jié)點線程安全地移動到同步隊列第练。
    當(dāng)節(jié)點移動到同步隊列后,當(dāng)前線程再使用LockSupport喚醒該節(jié)點的線程玛荞。

    被喚醒后的線程娇掏,將從await()方法中的while循環(huán)中退出isOnSyncQueue(Node node)方法返回true,節(jié)點已經(jīng)在同步隊列中勋眯,進而調(diào)用同步器的acquireQueued()方法加入到獲取同步狀態(tài)的競爭中驹碍。

    成功獲取同步狀態(tài)之后,被喚醒的線程將從先前調(diào)用的await()方法返回凡恍,此時該線程已經(jīng)成功地獲取了鎖。

    ConditionsignalAll()方法怔球,相當(dāng)于對等待隊列中的每個節(jié)點均執(zhí)行一次signal()方法嚼酝,效果就是將等待隊列中所有節(jié)點全部移動到同步隊列中,并喚醒每個節(jié)點的線程竟坛。


小結(jié)

  • Lock接口提供的方法lock()闽巩、unlock()等獲取和釋放鎖的介紹
  • 隊列同步器的使用 以及 自定義隊列同步器
  • 重入鎖 的使用和實現(xiàn)介紹
  • 讀寫鎖 的 讀鎖 和 寫鎖
  • LockSupport工具實現(xiàn) 阻塞和喚醒線程
  • Condition接口實現(xiàn) 等待/通知模式

以上

</article>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钧舌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子涎跨,更是在濱河造成了極大的恐慌洼冻,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隅很,死亡現(xiàn)場離奇詭異撞牢,居然都是意外死亡,警方通過查閱死者的電腦和手機叔营,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門屋彪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绒尊,你說我怎么就攤上這事畜挥。” “怎么了婴谱?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵蟹但,是天一觀的道長。 經(jīng)常有香客問我谭羔,道長华糖,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任口糕,我火速辦了婚禮缅阳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘景描。我一直安慰自己十办,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布超棺。 她就那樣靜靜地躺著向族,像睡著了一般。 火紅的嫁衣襯著肌膚如雪棠绘。 梳的紋絲不亂的頭發(fā)上件相,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音氧苍,去河邊找鬼夜矗。 笑死,一個胖子當(dāng)著我的面吹牛让虐,可吹牛的內(nèi)容都是我干的紊撕。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼赡突,長吁一口氣:“原來是場噩夢啊……” “哼对扶!你這毒婦竟也來了摘悴?” 一聲冷哼從身側(cè)響起锣夹,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤崩溪,失蹤者是張志新(化名)和其女友劉穎尾膊,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體络凿,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡骡送,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了喷众。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片各谚。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖到千,靈堂內(nèi)的尸體忽然破棺而出昌渤,到底是詐尸還是另有隱情,我是刑警寧澤憔四,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布膀息,位于F島的核電站,受9級特大地震影響了赵,放射性物質(zhì)發(fā)生泄漏潜支。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一柿汛、第九天 我趴在偏房一處隱蔽的房頂上張望冗酿。 院中可真熱鬧,春花似錦络断、人聲如沸裁替。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弱判。三九已至,卻和暖如春锥惋,著一層夾襖步出監(jiān)牢的瞬間昌腰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工膀跌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遭商,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓捅伤,卻偏偏與公主長得像株婴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1.Lock接口 一般來說困介,一個鎖能夠防止多個線程同時訪問共享資源(但有些鎖可以允許多個線程并發(fā)的訪問共享資源,比...
    加夕閱讀 460評論 0 1
  • Lock接口 需要顯式的獲取和釋放鎖蘸际,支持非阻塞的獲取鎖座哩,支持中斷的獲取鎖,支持超時獲取鎖粮彤; Synchronze...
    星冉子閱讀 133評論 0 0
  • 主要內(nèi)容有:Lock接口隊列同步器重入鎖讀寫鎖LockSupport工具Condition接口 1.Lock接口 ...
    htkeepmoving閱讀 257評論 0 1
  • 一根穷、概述 鎖的使用與實現(xiàn) Lock接口(顯式地獲取鎖和釋放鎖) 擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超...
    康俊1024閱讀 312評論 0 0
  • 1.Lock 接口 ??鎖是用來控制多個線程訪問共享資源的方式导坟,一般來說屿良, 一個鎖能夠防止多個線程同時訪問共享資源...
    ShayHe閱讀 390評論 0 0