死磕 java同步系列之AQS起篇

問題

(1)AQS是什麼?java

(2)AQS的定位?多線程

(3)AQS的實現原理?框架

(4)基於AQS實現本身的鎖?ide

簡介

AQS的全稱是AbstractQueuedSynchronizer,它的定位是爲Java中幾乎全部的鎖和同步器提供一個基礎框架。學習

AQS是基於FIFO的隊列實現的,而且內部維護了一個狀態變量state,經過原子更新這個狀態變量state便可以實現加鎖解鎖操做。ui

本章及後續章節的內容理解起來可能會比較晦澀,建議先閱讀彤哥上一章的內容【死磕 java同步系列之本身動手寫一個鎖Lock】。this

核心源碼

主要內部類

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;

    // 下一個等待在條件上的節點(Condition鎖時使用)
    Node nextWaiter;

    // 是不是共享模式
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    // 獲取前一個節點
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    // 節點的構造方法
    Node() {    // Used to establish initial head or SHARED marker
    }

    // 節點的構造方法
    Node(Thread thread, Node mode) {     // Used by addWaiter
        // 把共享模式仍是互斥模式存儲到nextWaiter這個字段裏面了
        this.nextWaiter = mode;
        this.thread = thread;
    }

    // 節點的構造方法
    Node(Thread thread, int waitStatus) { // Used by Condition
        // 等待的狀態,在Condition中使用
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

典型的雙鏈表結構,節點中保存着當前線程、前一個節點、後一個節點以及線程的狀態等信息。線程

主要屬性

// 隊列的頭節點
private transient volatile Node head;
// 隊列的尾節點
private transient volatile Node tail;
// 控制加鎖解鎖的狀態變量
private volatile int state;

定義了一個狀態變量和一個隊列,狀態變量用來控制加鎖解鎖,隊列用來放置等待的線程。code

注意,這幾個變量都要使用volatile關鍵字來修飾,由於是在多線程環境下操做,要保證它們的值修改以後對其它線程當即可見。隊列

這幾個變量的修改是直接使用的Unsafe這個類來操做的:

// 獲取Unsafe類的實例,注意這種方式僅限於jdk本身使用,普通用戶是沒法這樣調用的
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 狀態變量state的偏移量
private static final long stateOffset;
// 頭節點的偏移量
private static final long headOffset;
// 尾節點的偏移量
private static final long tailOffset;
// 等待狀態的偏移量(Node的屬性)
private static final long waitStatusOffset;
// 下一個節點的偏移量(Node的屬性)
private static final long nextOffset;

static {
    try {
        // 獲取state的偏移量
        stateOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        // 獲取head的偏移量
        headOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
        // 獲取tail的偏移量
        tailOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        // 獲取waitStatus的偏移量
        waitStatusOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("waitStatus"));
        // 獲取next的偏移量
        nextOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("next"));

    } catch (Exception ex) { throw new Error(ex); }
}

// 調用Unsafe的方法原子更新state
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

關於Unsafe類的講解請參考彤哥以前寫的【死磕 java魔法類之Unsafe解析】。

子類須要實現的主要方法

咱們能夠看到AQS的全稱是AbstractQueuedSynchronizer,它本質上是一個抽象類,說明它本質上應該是須要子類來實現的,那麼子類實現一個同步器須要實現哪些方法呢?

// 互斥模式下使用:嘗試獲取鎖
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
// 互斥模式下使用:嘗試釋放鎖
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
// 共享模式下使用:嘗試獲取鎖
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
// 共享模式下使用:嘗試釋放鎖
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}
// 若是當前線程獨佔着鎖,返回true
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

問題:這幾個方法爲何不直接定義成抽象方法呢?

由於子類只要實現這幾個方法中的一部分就能夠實現一個同步器了,因此不須要定義成抽象方法。

下面咱們經過一個案例來介紹AQS中的部分方法。

基於AQS本身動手寫一個鎖

直接上代碼:

public class MyLockBaseOnAqs {

    // 定義一個同步器,實現AQS類
    private static class Sync extends AbstractQueuedSynchronizer {
        // 實現tryAcquire(acquires)方法
        @Override
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        // 實現tryRelease(releases)方法
        @Override
        protected boolean tryRelease(int releases) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
    }

    // 聲明同步器
    private final Sync sync = new Sync();

    // 加鎖
    public void lock() {
        sync.acquire(1);
    }

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


    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        MyLockBaseOnAqs lock = new MyLockBaseOnAqs();

        CountDownLatch countDownLatch = new CountDownLatch(1000);

        IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
            lock.lock();

            try {
                IntStream.range(0, 10000).forEach(j -> {
                    count++;
                });
            } finally {
                lock.unlock();
            }
//            System.out.println(Thread.currentThread().getName());
            countDownLatch.countDown();
        }, "tt-" + i).start());

        countDownLatch.await();

        System.out.println(count);
    }
}

運行main()方法老是打印出10000000(一千萬),說明這個鎖也是能夠直接使用的,固然這也是一個不可重入的鎖。

是否是很簡單,只須要簡單地實現AQS的兩個方法就完成了上一章彤哥本身動手實現的鎖的功能。

它是怎麼實現的呢?

咱們這一章先不講源碼,後面學習了ReentrantLock天然就明白了。

總結

這一章就到此結束了,本篇沒有去深刻的解析AQS的源碼,筆者認爲這沒有必要,由於對於歷來都沒有看過鎖相關的源碼的同窗來講,一上來就講AQS的源碼確定會一臉懵逼的,具體的源碼咱們穿插在後面的鎖和同步器的部分來學習,等全部跟AQS相關的源碼學習完畢了,再來一篇總結。

下面總結一下這一章的主要內容:

(1)AQS是Java中幾乎全部鎖和同步器的一個基礎框架,這裏說的是「幾乎」,由於有極個別確實沒有經過AQS來實現;

(2)AQS中維護了一個隊列,這個隊列使用雙鏈表實現,用於保存等待鎖排隊的線程;

(3)AQS中維護了一個狀態變量,控制這個狀態變量就能夠實現加鎖解鎖操做了;

(4)基於AQS本身動手寫一個鎖很是簡單,只須要實現AQS的幾個方法便可。

彩蛋

上一章彤哥本身動手寫的鎖,其實能夠當作是AQS的一個縮影,看懂了那個基本上AQS能夠看懂一半了,由於彤哥那個裏面沒有寫Condition相關的內容,下一章ReentrantLock重入鎖中咱們將一塊兒學習Condition相關的內容。

因此呢,仍是建議你們去看看這篇文章,點擊下面的推薦閱讀能夠直達。

推薦閱讀

  1. 死磕 java同步系列之本身動手寫一個鎖Lock

  2. 死磕 java魔法類之Unsafe解析

  3. 死磕 java同步系列之JMM(Java Memory Model)

  4. 死磕 java同步系列之volatile解析

  5. 死磕 java同步系列之synchronized解析


歡迎關注個人公衆號「彤哥讀源碼」,查看更多源碼系列文章, 與彤哥一塊兒暢遊源碼的海洋。

qrcode

相關文章
相關標籤/搜索