中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

死磕 java同步系列之AQS起篇

發布時間:2020-07-01 13:41:43 來源:網絡 閱讀:187 作者:彤哥讀源碼 欄目:編程語言

問題

(1)AQS是什么?

(2)AQS的定位?

(3)AQS的實現原理?

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

簡介

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

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

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

核心源碼

主要內部類

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;

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

注意,這幾個變量都要使用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解析

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

死磕 java同步系列之AQS起篇

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

沂水县| 敦煌市| 西贡区| 博湖县| 沛县| 黔西县| 洛南县| 万州区| 镇雄县| 上饶县| 勐海县| 门源| 张掖市| 从江县| 来安县| 潮州市| 景东| 恩施市| 中江县| 沾益县| 延安市| 讷河市| 怀远县| 东山县| 肥东县| 安徽省| 吉林省| 攀枝花市| 巴塘县| 宾阳县| 舟山市| 桐城市| 辉南县| 石泉县| 汤原县| 化隆| 永清县| 洛南县| 湖南省| 丹江口市| 泰州市|