日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

深入理解AQS之獨占鎖ReentrantLock源碼分析

作者:bingtanghulu_6 更新時間: 2022-05-11 編程語言

目錄

1.java為什么要實現自己的管程機制?

2.AQS

?

2.1 state

?2.2 同步等待隊列和條件等待隊列

?2.3 自定義AQS

3.reentrantLock詳解

3.1 源碼分析(TODO)


1.java為什么要實現自己的管程機制?

管程:操作系統底層實現的對線程并發問題實現的監視器機制,管程內部大致分為:同步等待隊列和等待喚醒隊列。

同步等待隊列的實現:synchronized在jvm底層保證線程能夠串行化的操作同步代碼塊內的程序。

等待喚醒隊列的實現:java在object類中實現了monitor機制,每個對象都有一個monitor,可以手動調用wait/notify/notifyall方法操作線程。

synchronized缺點:不能手動加鎖解鎖,synchronized內部加解鎖是自動的。

為了解決以上場景java實現了自己的管程機制AQS機制:

同步等待隊列(雙向鏈表結構):cas實現(內部通過volatile int state狀態控制入隊出隊) 加解鎖有自己的實現

條件等待隊列(單向鏈表結構):Condition接口實現(await/signal/signalAll等待;喚醒機制)

2.AQS

基于上述描述講解AQS。

AQS的特性:

? ? ? ? 1. 阻塞等待隊列

? ? ? ? 2. 共享/獨占

? ? ? ? 3. 公平/非公平

? ? ? ? 4. 可重入

? ? ? ? 5. 允許中斷

AQS的實現類如下圖。

AQS的資源共享方式

????????獨占:只有一個線程能執行,如reentrantLock

????????共享:多個線程同時執行,如Semaphore和CountDownLatch

AQS的兩種隊列:同步等待隊列和條件等待隊列

????????同步等待隊列:主要用于維護獲取鎖失敗的線程

????????條件等待隊列:調用await時會釋放鎖進入條件等待隊列,調用signal時喚醒線程放入同步等待隊列

AQS中實現的方法

????????共享:tryAcquireShared獲取共享鎖,releaseShared解鎖,tryReleaseShared嘗試解鎖

????????獨占:tryAcquire獲取獨占鎖,release解鎖,tryRelease嘗試解鎖

2.1 state

AQS中volatile int修飾的state:表示AQS的狀態

state的訪問方式:

getState(),setState(),compareAndSetState()

?

?2.2 同步等待隊列和條件等待隊列

同步等待隊列是一個雙向鏈表的隊列。

條件等待隊列是一個單向鏈表結構,條件調用await喚醒以后進入同步等待隊列,同步等待隊列阻塞進入條件等待隊列。

?2.3 自定義AQS

自定義自己的AQS實現類:

1.繼承AbstractQueuedSynchronizer

2.實現自己的抽象方法tryAcquire和tryRelease方法。其他方法都是AQS已經封裝好的直接調用即可。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class TestLock extends AbstractQueuedSynchronizer {

    private int state_0 = 0;//0-解鎖
    private int state_1 = 1;//1-加鎖

    //加鎖邏輯:使用CAS實現,如果修改成功返回true并且設置獨占鎖,否則返回false
    @Override
    protected boolean tryAcquire(int arg) {
        if(compareAndSetState(state_0,arg)){
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }

    //釋放鎖:將鎖狀態還原
    @Override
    protected boolean tryRelease(int arg) {
        setExclusiveOwnerThread(null);
        setState(arg);
        return true;
    }

    public void lock(){
        acquire(state_1);
    }

    public boolean tryLock(){
        return tryAcquire(state_1);
    }

    public void unLock(){
        release(state_0);
    }

    public boolean tryUnLock(){
        return tryRelease(state_0);
    }
}

class TestAQS{
    private static int sum = 0;

    private static TestLock testLock = new TestLock();

    public static void main(String[] args) throws InterruptedException {
        for (int i =0 ;i<3;i++){
            Thread thread = new Thread(()->{
                testLock.lock();
                try{
                    for (int j=0;j<10000;j++){
                        sum++;
                    }
                }finally {
                    testLock.unLock();
                }

            });
            thread.start();
        }

        //休眠1秒等待線程執行完畢
        Thread.sleep(1000);
        System.out.println("sum:"+sum);
    }
}

3.reentrantLock詳解

reentrantLock是一個基于AQS框架的鎖,可以手動加解鎖,比synchronized性能要好一點,支持解決并發安全問題,支持公平和非公平鎖。

synchronized與reenTrantLock的區別:最重要的區別就是reenTrantLock需要在finally塊中手動解鎖。

reenTrantLock的特點:

? ? ? ? 1.可中斷

? ? ? ? 2.可設置超時時間

? ? ? ? 3. 可以設置公平鎖

? ? ? ? 4. 支持多個條件變量

? ? ? ? 5. 支持可重入

3.1 源碼分析(TODO)

關注點:

1.加解鎖

2.公平,非公平,可重入實現

3.線程競爭鎖失敗入隊阻塞邏輯和獲取鎖的線程釋放鎖喚醒阻塞線程競爭鎖的邏輯實現

重點debug下面代碼:lock.lock和lock.unlock源碼部分

1.lock.lock(加鎖,默認非公平鎖實現):CAS判斷,如果修改成功,設置獨占鎖,沒有走公平鎖邏輯獲取鎖。

? ? ? ? 1.1 公平鎖邏輯分為幾步:

? ? ? ? ? ? ? ? 1.1.1 嘗試獲取鎖(tryAcquire(int acquires)),分為兩步:1.線程狀態state為0執行CAS獨占鎖邏輯。2.如果當前線程是獨占鎖執行重入鎖邏輯,將state加1。

? ? ? ? ? ? ? ? 1.1.2 入隊(addWaiter(Node.EXCLUSIVE)),桟入隊操作有點兒復雜(TODO),簡單來說就是雙向鏈表的建立過程。

? ? ? ? ? ? ? ? 1.1.3 獲取隊列(boolean acquireQueued(final Node node, int arg)),也是分為兩步:1.如果是只有一個節點并且獲取鎖成功返回中斷標志位為false;2.如果不是只有一個節點通過waitstatus標志位(-1 喚醒鎖,大于0)回去嘗試喚醒鎖或者(TODO,看不懂了,感覺有兩個隊列)

? ? ? ? 1.2 設置中斷標志位(selfInterrupt()):前面的操作會取消中斷標志位,這一步是為了復位中斷標志位

2.lock.unlock(解鎖):解鎖邏輯很簡單,分為兩步

???2.1 嘗試將重入標志state值減1判斷是否等于0,tryRelease(arg),如果為0,復原獨占鎖為null

? ? ? ? 2.2? 上一步的state為0時并且鏈表不為空,喚醒標志位waitstatus不為0,阻塞線程

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {

    private static  int sum = 0;
    private static Lock lock = new ReentrantLock();
    //private static TulingLock lock = new TulingLock();

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(()->{
                //加鎖
                lock.lock();
                try {
                    // 臨界區代碼
                    // TODO 業務邏輯:讀寫操作不能保證線程安全
                    for (int j = 0; j < 10000; j++) {
                        sum++;
                    }
                } finally {
                    // 解鎖
                    lock.unlock();
                }
            });
            thread.start();
        }

        Thread.sleep(2000);
        System.out.println(sum);
    }
}

原文鏈接:https://blog.csdn.net/qq_21575929/article/details/124581277

欄目分類
最近更新