成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

java同步器AQS的實(shí)現(xiàn)原理是什么

這篇文章給大家介紹java同步器AQS的實(shí)現(xiàn)原理是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

創(chuàng)新互聯(lián)建站主營建安網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,重慶APP軟件開發(fā),建安h5成都小程序開發(fā)搭建,建安網(wǎng)站營銷推廣歡迎建安等地區(qū)企業(yè)咨詢

前言

在java.util.concurrent.locks包中有很多Lock的實(shí)現(xiàn)類,常用的有ReentrantLock、ReadWriteLock(實(shí)現(xiàn)類ReentrantReadWriteLock),內(nèi)部實(shí)現(xiàn)都依賴AbstractQueuedSynchronizer類,接下去讓我們看看Doug Lea大神是如何使用一個普通類就完成了代碼塊的并發(fā)訪問控制。為了方便,本文中使用AQS代替AbstractQueuedSynchronizer。

定義

public abstract class AbstractQueuedSynchronizer extends
    AbstractOwnableSynchronizer implements java.io.Serializable { 
    //等待隊列的頭節(jié)點(diǎn)
    private transient volatile Node head;    //等待隊列的尾節(jié)點(diǎn)
    private transient volatile Node tail;    //同步狀態(tài)
    private volatile int state;    
   protected final int getState() { return state;}    
   protected final void setState(int newState) { state = newState;}    ... }

隊列同步器AQS是用來構(gòu)建鎖或其他同步組件的基礎(chǔ)框架,內(nèi)部使用一個int成員變量表示同步狀態(tài),通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作,其中內(nèi)部狀態(tài)state,等待隊列的頭節(jié)點(diǎn)head和尾節(jié)點(diǎn)head,都是通過volatile修飾,保證了多線程之間的可見。

在深入實(shí)現(xiàn)原理之前,我們先看看內(nèi)部的FIFO隊列是如何實(shí)現(xiàn)的。

static final class Node {        
   static final Node SHARED = new Node();        
   static final Node EXCLUSIVE = null;        
   static final int CANCELLED =  1;        
   static final int SIGNAL    = -1;        
   static final int CONDITION = -2;        
   static final int PROPAGATE = -3;        
   volatile int waitStatus;        
   volatile Node prev;        
   volatile Node next;        
   volatile Thread thread;    Node nextWaiter;    ...    }

先來一張形象的圖(該圖其實(shí)是網(wǎng)上找的)

java同步器AQS的實(shí)現(xiàn)原理是什么

黃色節(jié)點(diǎn)是默認(rèn)head節(jié)點(diǎn),其實(shí)是一個空節(jié)點(diǎn),我覺得可以理解成代表當(dāng)前持有鎖的線程,每當(dāng)有線程競爭失敗,都是插入到隊列的尾節(jié)點(diǎn),tail節(jié)點(diǎn)始終指向隊列中的最后一個元素。

每個節(jié)點(diǎn)中, 除了存儲了當(dāng)前線程,前后節(jié)點(diǎn)的引用以外,還有一個waitStatus變量,用于描述節(jié)點(diǎn)當(dāng)前的狀態(tài)。多線程并發(fā)執(zhí)行時,隊列中會有多個節(jié)點(diǎn)存在,這個waitStatus其實(shí)代表對應(yīng)線程的狀態(tài):有的線程可能獲取鎖因為某些原因放棄競爭;有的線程在等待滿足條件,滿足之后才能執(zhí)行等等。一共有4中狀態(tài):

  1. CANCELLED 取消狀態(tài)

  2. SIGNAL 等待觸發(fā)狀態(tài)

  3. CONDITION 等待條件狀態(tài)

  4. PROPAGATE 狀態(tài)需要向后傳播

等待隊列是FIFO先進(jìn)先出,只有前一個節(jié)點(diǎn)的狀態(tài)為SIGNAL時,當(dāng)前節(jié)點(diǎn)的線程才能被掛起。

實(shí)現(xiàn)原理

子類重寫tryAcquire和tryRelease方法通過CAS指令修改狀態(tài)變量state。

public final void acquire(int arg) {   
 if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))    
    selfInterrupt();
}
線程獲取鎖過程

下列步驟中線程A和B進(jìn)行競爭。

  1. 線程A執(zhí)行CAS執(zhí)行成功,state值被修改并返回true,線程A繼續(xù)執(zhí)行。

  2. 線程A執(zhí)行CAS指令失敗,說明線程B也在執(zhí)行CAS指令且成功,這種情況下線程A會執(zhí)行步驟3。

  3. 生成新Node節(jié)點(diǎn)node,并通過CAS指令插入到等待隊列的隊尾(同一時刻可能會有多個Node節(jié)點(diǎn)插入到等待隊列中),如果tail節(jié)點(diǎn)為空,則將head節(jié)點(diǎn)指向一個空節(jié)點(diǎn)(代表線程B),具體實(shí)現(xiàn)如下:

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;    
   if (pred != null) {        node.prev = pred;        
       if (compareAndSetTail(pred, node)) {            pred.next = node;            
           return node;        }    }    enq(node);    
   return node; }
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;            }        }    } }
  1. node插入到隊尾后,該線程不會立馬掛起,會進(jìn)行自旋操作。因為在node的插入過程,線程B(即之前沒有阻塞的線程)可能已經(jīng)執(zhí)行完成,所以要判斷該node的前一個節(jié)點(diǎn)pred是否為head節(jié)點(diǎn)(代表線程B),如果pred == head,表明當(dāng)前節(jié)點(diǎn)是隊列中第一個“有效的”節(jié)點(diǎn),因此再次嘗試tryAcquire獲取鎖,
    1、如果成功獲取到鎖,表明線程B已經(jīng)執(zhí)行完成,線程A不需要掛起。
    2、如果獲取失敗,表示線程B還未完成,至少還未修改state值。進(jìn)行步驟5。

final boolean acquireQueued(final Node node, int arg) {    
   boolean failed = true;    
   try {        
       boolean interrupted = false;        
       for (;;) {            
           final Node p = node.predecessor();            
           if (p == head && tryAcquire(arg)) {                setHead(node);                p.next = null; // help GC                failed = false;                
               return interrupted;            }            
           if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())                interrupted = true;        }    } finally {        
       if (failed)            cancelAcquire(node);    } }
  1. 前面我們已經(jīng)說過只有前一個節(jié)點(diǎn)pred的線程狀態(tài)為SIGNAL時,當(dāng)前節(jié)點(diǎn)的線程才能被掛起。
    1、如果pred的waitStatus == 0,則通過CAS指令修改waitStatus為Node.SIGNAL。
    2、如果pred的waitStatus > 0,表明pred的線程狀態(tài)CANCELLED,需從隊列中刪除。
    3、如果pred的waitStatus為Node.SIGNAL,則通過LockSupport.park()方法把線程A掛起,并等待被喚醒,被喚醒后進(jìn)入步驟6。
    具體實(shí)現(xiàn)如下:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {    
   int ws = pred.waitStatus;    if (ws == Node.SIGNAL)        
       /*         * This node has already set status asking a release         * to signal it, so it can safely park.         */        return true;    if (ws > 0) {        
       /*         * Predecessor was cancelled. Skip over predecessors and         * indicate retry.         */        do {            node.prev = pred = pred.prev;        } while (pred.waitStatus > 0);        pred.next = node;    } else {        
       /*         * 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; }
  1. 線程每次被喚醒時,都要進(jìn)行中斷檢測,如果發(fā)現(xiàn)當(dāng)前線程被中斷,那么拋出InterruptedException并退出循環(huán)。從無限循環(huán)的代碼可以看出,并不是被喚醒的線程一定能獲得鎖,必須調(diào)用tryAccquire重新競爭,因為鎖是非公平的,有可能被新加入的線程獲得,從而導(dǎo)致剛被喚醒的線程再次被阻塞,這個細(xì)節(jié)充分體現(xiàn)了“非公平”的精髓。


線程釋放鎖過程:
  1. 如果頭結(jié)點(diǎn)head的waitStatus值為-1,則用CAS指令重置為0;

  2. 找到waitStatus值小于0的節(jié)點(diǎn)s,通過LockSupport.unpark(s.thread)喚醒線程。

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;    
   if (ws < 0)        compareAndSetWaitStatus(node, ws, 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;      
   if (s == null || s.waitStatus > 0) {        s = null;        
       for (Node t = tail; t != null && t != node; t = t.prev)            if (t.waitStatus <= 0)                s = t;    }    
   if (s != null)        LockSupport.unpark(s.thread); }

關(guān)于java同步器AQS的實(shí)現(xiàn)原理是什么就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

網(wǎng)頁題目:java同步器AQS的實(shí)現(xiàn)原理是什么
瀏覽路徑:http://jinyejixie.com/article24/iiseje.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供面包屑導(dǎo)航、移動網(wǎng)站建設(shè)、App設(shè)計、網(wǎng)站建設(shè)、網(wǎng)站設(shè)計、定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

商城網(wǎng)站建設(shè)
肃南| 吴川市| 芒康县| 桐梓县| 巴林右旗| 鹰潭市| 宁远县| 孝昌县| 吉安市| 长葛市| 任丘市| 汉阴县| 寿宁县| 从江县| 东至县| 德格县| 西乌珠穆沁旗| 曲阜市| 福安市| 陕西省| 桐庐县| 吉隆县| 交城县| 赤壁市| 师宗县| 佛学| 淮南市| 常山县| 祁门县| 嘉善县| 泰来县| 卢龙县| 张家川| 锦屏县| 泸州市| 泰安市| 浑源县| 永昌县| 娄底市| 高平市| 龙州县|