死磕java同步系列之AQS起篇

問題

(1)AQS是什么?

創(chuàng)新互聯(lián)建站專注于子長網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供子長營銷型網(wǎng)站建設(shè),子長網(wǎng)站制作、子長網(wǎng)頁設(shè)計、子長網(wǎng)站官網(wǎng)定制、小程序開發(fā)服務(wù),打造子長網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供子長網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

(2)AQS的定位?

(3)AQS的實現(xiàn)原理?

(4)基于AQS實現(xiàn)自己的鎖?

簡介

AQS的全稱是AbstractQueuedSynchronizer,它的定位是為Java中幾乎所有的鎖和同步器提供一個基礎(chǔ)框架。

AQS是基于FIFO的隊列實現(xiàn)的,并且內(nèi)部維護了一個狀態(tài)變量state,通過原子更新這個狀態(tài)變量state即可以實現(xiàn)加鎖解鎖操作。

本章及后續(xù)章節(jié)的內(nèi)容理解起來可能會比較晦澀,建議先閱讀彤哥上一章的內(nèi)容【死磕 java同步系列之自己動手寫一個鎖Lock】。

核心源碼

主要內(nèi)部類

static final class Node {
    // 標識一個節(jié)點是共享模式
    static final Node SHARED = new Node();
    // 標識一個節(jié)點是互斥模式
    static final Node EXCLUSIVE = null;

    // 標識線程已取消
    static final int CANCELLED =  1;
    // 標識后繼節(jié)點需要喚醒
    static final int SIGNAL    = -1;
    // 標識線程等待在一個條件上
    static final int CONDITION = -2;
    // 標識后面的共享鎖需要無條件的傳播(共享鎖需要連續(xù)喚醒讀的線程)
    static final int PROPAGATE = -3;

    // 當前節(jié)點保存的線程對應(yīng)的等待狀態(tài)
    volatile int waitStatus;

    // 前一個節(jié)點
    volatile Node prev;

    // 后一個節(jié)點
    volatile Node next;

    // 當前節(jié)點保存的線程
    volatile Thread thread;

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

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

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

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

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

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

典型的雙鏈表結(jié)構(gòu),節(jié)點中保存著當前線程、前一個節(jié)點、后一個節(jié)點以及線程的狀態(tài)等信息。

主要屬性

// 隊列的頭節(jié)點
private transient volatile Node head;
// 隊列的尾節(jié)點
private transient volatile Node tail;
// 控制加鎖解鎖的狀態(tài)變量
private volatile int state;

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

注意,這幾個變量都要使用volatile關(guān)鍵字來修飾,因為是在多線程環(huán)境下操作,要保證它們的值修改之后對其它線程立即可見。

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

// 獲取Unsafe類的實例,注意這種方式僅限于jdk自己使用,普通用戶是無法這樣調(diào)用的
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 狀態(tài)變量state的偏移量
private static final long stateOffset;
// 頭節(jié)點的偏移量
private static final long headOffset;
// 尾節(jié)點的偏移量
private static final long tailOffset;
// 等待狀態(tài)的偏移量(Node的屬性)
private static final long waitStatusOffset;
// 下一個節(jié)點的偏移量(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); }
}

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

關(guān)于Unsafe類的講解請參考彤哥之前寫的【死磕 java魔法類之Unsafe解析】。

子類需要實現(xiàn)的主要方法

我們可以看到AQS的全稱是AbstractQueuedSynchronizer,它本質(zhì)上是一個抽象類,說明它本質(zhì)上應(yīng)該是需要子類來實現(xiàn)的,那么子類實現(xiàn)一個同步器需要實現(xiàn)哪些方法呢?

// 互斥模式下使用:嘗試獲取鎖
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();
}

問題:這幾個方法為什么不直接定義成抽象方法呢?

因為子類只要實現(xiàn)這幾個方法中的一部分就可以實現(xiàn)一個同步器了,所以不需要定義成抽象方法。

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

基于AQS自己動手寫一個鎖

直接上代碼:

public class MyLockBaseOnAqs {

    // 定義一個同步器,實現(xiàn)AQS類
    private static class Sync extends AbstractQueuedSynchronizer {
        // 實現(xiàn)tryAcquire(acquires)方法
        @Override
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        // 實現(xiàn)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(一千萬),說明這個鎖也是可以直接使用的,當然這也是一個不可重入的鎖。

是不是很簡單,只需要簡單地實現(xiàn)AQS的兩個方法就完成了上一章彤哥自己動手實現(xiàn)的鎖的功能。

它是怎么實現(xiàn)的呢?

我們這一章先不講源碼,后面學習了ReentrantLock自然就明白了。

總結(jié)

這一章就到此結(jié)束了,本篇沒有去深入的解析AQS的源碼,筆者認為這沒有必要,因為對于從來都沒有看過鎖相關(guān)的源碼的同學來說,一上來就講AQS的源碼肯定會一臉懵逼的,具體的源碼我們穿插在后面的鎖和同步器的部分來學習,等所有跟AQS相關(guān)的源碼學習完畢了,再來一篇總結(jié)。

下面總結(jié)一下這一章的主要內(nèi)容:

(1)AQS是Java中幾乎所有鎖和同步器的一個基礎(chǔ)框架,這里說的是“幾乎”,因為有極個別確實沒有通過AQS來實現(xiàn);

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

(3)AQS中維護了一個狀態(tài)變量,控制這個狀態(tài)變量就可以實現(xiàn)加鎖解鎖操作了;

(4)基于AQS自己動手寫一個鎖非常簡單,只需要實現(xiàn)AQS的幾個方法即可。

彩蛋

上一章彤哥自己動手寫的鎖,其實可以看成是AQS的一個縮影,看懂了那個基本上AQS可以看懂一半了,因為彤哥那個里面沒有寫Condition相關(guān)的內(nèi)容,下一章ReentrantLock重入鎖中我們將一起學習Condition相關(guān)的內(nèi)容。

所以呢,還是建議大家去看看這篇文章,點擊下面的推薦閱讀可以直達。

推薦閱讀

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

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

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

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

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

歡迎關(guān)注我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢游源碼的海洋。

死磕 java同步系列之AQS起篇

網(wǎng)站欄目:死磕java同步系列之AQS起篇
鏈接地址:http://muchs.cn/article2/jcpsic.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計公司、網(wǎng)站排名手機網(wǎng)站建設(shè)、網(wǎng)站收錄、域名注冊企業(yè)網(wǎng)站制作

廣告

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

搜索引擎優(yōu)化