天天看點

Java并發程式設計-隊列同步器(AbstractQueuedSynchronizer)

章節目錄

  • Lock接口與Synchronized的差別及特性
  • 隊列同步器的接口與自定義鎖示例
  • 隊列同步器的實作分析

1.Lock接口與Synchronized的差別及特性

特性 描述
嘗試非阻塞性的擷取鎖 目前線程嘗試擷取鎖(自旋擷取鎖),如果這一時刻鎖沒有被其他線程擷取到,則成功擷取并持有鎖
能被中斷的擷取鎖 已擷取鎖的線程可以響應中斷,當擷取到鎖的線程被中斷時,可以抛出中斷異常,同時鎖會被釋放
逾時擷取鎖 在指定的截止時間之前擷取鎖,如果截止時間到了仍然沒有擷取到鎖,則傳回

注意:Lock接口的實作基本上都是通過聚合了一個同步器的子類來完成線程通路控制的

隊裡同步器的接口與定義鎖示例

隊列同步器定義:

隊列同步器,是用來建構鎖與其它同步元件的基礎架構,基本資料結構與内容是:
1、int state -> state 标示同步狀态;
2、内置的FIFO來完成擷取同步狀态的線程的排隊工作。
           

隊列同步器使用方式

1、子類通過繼承同步器并實作它的抽象方法來管理同步狀态;
2、實作過程中對同步狀态的更改,通過
setState()、
setState(int newState)、
compareAndSetState(int expect,int newUpdateValue)
來進行操作,保證狀态改變時原子性的、安全的;
3、實作同步器的子類被推薦為自定義同步元件的靜态内部類;
4、同步器可以支援獨占式的擷取同步狀态(ReentrantLock)、也可以支援共享
式的擷取同步狀态(ReentrantReadWriteLock)
           

對于同步器與鎖的關系可以這樣了解:

  • 在鎖的實作中聚合同步器,利用同步器實作鎖的語義。
  • 鎖面向使用者,它定義了使用者與鎖的互動接口,隐藏了實作細節。
  • 同步器面向的是鎖的實作者,它簡化了鎖的實作方式,屏蔽了同步器狀态管理、線程排隊、等待與喚醒等底層操作。

2.隊列同步器的接口與自定義鎖示例

2.1 模闆方法模式

同步器的設定是基于**模版方法模式**,使用者需要繼承同步器并重寫指定的方
法,随後将同步器組合在自定義同步元件的實作中,并調用同步器提供的模闆
方法,而這些模闆方法将會調用使用者重寫的方法。
           

2.2 重寫同步器指定的方法

getState():擷取目前同步狀态
setState(int newState):設定目前同步狀态
compareAndSetState(int expect,int update): 使用CAS設定目前的狀态,該方
法保證狀态設定的原子性
           

2.3 同步器可重寫的方法

方法名稱
protected boolean tryAcquire(int arg) 獨占式擷取同步狀态,實作該方法需要查詢目前狀态并判斷同步狀态是否符合預期,然後再進行CAS設定同步狀态
protected boolean tryRelease(int arg) 獨占式釋放同步狀态,等待擷取同步狀态的線程将有機會擷取同步狀态(公平性擷取鎖)
protected int tryAcquireShared(int arg) 共享式擷取同步狀态,傳回>=0的值,标示擷取成功,反之擷取失敗
protected boolean tryReleaseShared(int arg) 共享式釋放同步狀态
protected boolean isHeldExclusively() 目前同步器是否在獨占模式下被線程占用,一般該方法表示是否被目前線程所獨占

2.4 獨占鎖示例

package org.seckill.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 利用了模闆方法模式
 */
public class Mutex implements Lock {

    private static class Sync extends AbstractQueuedSynchronizer {
        //是否處于占用狀态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        //當狀态為0時擷取鎖
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //釋放鎖,将目前狀态設定為0
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;

        }

        //傳回一個condition,每個condition中都包含了一個condition隊列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    //僅需要将操作代理到Sync上即可
    private Sync sync = new Sync();

    public void lock() {
        sync.acquire(1);//調用tryAccquire
    }

    //目前已擷取鎖的線程響應中斷,釋放鎖,抛出異常,并傳回
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);//嘗試立即擷取鎖
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));//嘗試逾時擷取鎖
    }

    public void unlock() {
        sync.release(1);//釋放鎖
    }

    public Condition newCondition() {
        return sync.newCondition();
    }
}

           

總結-實作同步元件的方法

1. 獨占鎖Mutex 是一個自定義同步元件,它允許同一時刻隻允許同一個線程占有鎖。
2.Mutex中定義了一個私有靜态内部類,該類繼承了同步器并實作了獨占式擷取和釋放同步狀态。
3.在tryAcquire(int acquires)方法中,經過CAS設定成功(同步狀态設定為1),則
代表擷取了同步狀态,而在tryRelease(int releases) 方法中隻是将同步狀态重
置為0。
           

3 隊列同步器的實作分析

3.1 同步隊列資料結構

  • 同步器依賴内部的同步隊列,即一個FIFO的隊列,這個隊列由雙向連結清單實作。節點資料從 隊列尾部插入,頭部删除。
  • node 資料結構
struct node {
        node prev; //節點前驅節點
        node next; //節點後繼節點
        Thread thread; //擷取同步狀态的線程
        int waitStatus;  //等待狀态
        Node nextWaiter; //等待隊列中的後繼節點
   }
           

等待隊列 後續篇章介紹到condition會有相關記錄。

Java并發程式設計-隊列同步器(AbstractQueuedSynchronizer)

同步隊列基本結構

3.2 無法擷取到同步狀态的線程節點被加入到同步隊列的尾部

本質上是采用 compareAndSetTail(Node expect,Node update),當一個線程成功的擷取了同步狀态
(或者鎖),其他線程将無法擷取到同步狀态,轉而被構造成為節點并加入到同步隊列中,而這個加入隊列的過程
必須要保證線程安全。是以采用了基于CAS的方式來設定尾節點的方法。
,需要傳遞目前節點認為的尾節點和目前節點,隻有設定成功後,目前節點才正式與之前的尾節點建立關聯。
           

3.3 成功擷取同步狀态

同步隊列遵循FIFO,首節點是擷取同步狀态成功的節點,首節點的線程在釋放
同步狀态時,會喚醒後繼節點,而後繼節點将會在擷取同步狀态成功時,将自己設定為首節點。
           

3.4 獨占式同步狀态擷取與釋放

  • 前驅節點為頭節點且能夠擷取同步狀态的判斷條件和線程進入同步隊列 來獲

    取同步狀态是自旋的過程。

  • 設定首節點是通過擷取同步狀态成功的線程來完成的acquireQueued(node,args)完成的

獨占式擷取同步狀态的流程圖

Java并發程式設計-隊列同步器(AbstractQueuedSynchronizer)

獨占式同步狀态(鎖)擷取流程