顯示鎖和AQS

JAVA JDK并發(fā)包里面提供了 synchronized關鍵字和Lock接口叽奥,synchronized關鍵字從語言層面為開發(fā)者提供了鎖杈抢,是隱式鎖,鎖的獲取和釋放全部被封裝起來了缘挽,很笨重掖肋,不能提供可中斷的獲取鎖屡穗,也不能實現等待超時模式獲取鎖撒蟀,而且是獨占鎖虽风,如果我們有可中斷的獲取咆瘟、等待超時模式獲取嚼隘、獲取共享鎖的需求,可以自己實現Lock接口來改寫袒餐。Lock鎖是顯示鎖飞蛹,使用需要遵循一些范式;

lock.lock();
try{
}finally{
  lock.unlock;
}

這里有個要點灸眼,解鎖操作必須要放在finally代碼塊里面卧檐,加鎖操作不能放在try代碼塊里面;前者是防止出錯之后鎖不能正常釋放焰宣,后者是防止出錯不能正常加鎖霉囚,反而解鎖了;

JDK顯示鎖 提供了 ReentrantLock(可重入鎖)匕积,ReentrantReadWriteLock(讀寫鎖)兩個常用的實現類盈罐;
鎖的可重入:當鎖作用的代碼塊發(fā)生遞歸的時候,已經加鎖的線程還是可以獲取原來的鎖闪唆,不會因為是遞歸就要繼續(xù)等待盅粪,JDK提供的所有鎖都是支持可重入的;
ReentrantReadWriteLock:讀寫鎖悄蕾,內部維護了readLock票顾,writeLock兩個鎖,讀鎖是共享鎖帆调,和其他讀鎖共享库物,和寫鎖互斥;寫鎖是獨占鎖贷帮,和其他線程的寫鎖戚揭、所有讀鎖互斥;當線程拿到讀鎖時撵枢,其他線程可以繼續(xù)獲取讀鎖民晒;當讀鎖存在時精居,寫鎖阻塞,等到所有讀鎖釋放之后才可以拿到寫鎖潜必,而這時其他線程獲取鎖的時候阻塞靴姿,拿不到任何鎖;當寫鎖釋放時磁滚,讀鎖喚醒佛吓,循環(huán)這一操作;讀寫鎖是針對讀多寫少的時候垂攘,可以大大提高服務器響應的時間维雇;因為讀狀態(tài)是共享鎖,只有寫的時候需要等待寫線程完成晒他,然后其他讀線程可以讀到最新狀態(tài)吱型;

什么是AQS?

AQS是一種隊列同步器,是用來構建鎖或者其他同步組件的基礎框架陨仅,它使用一個int成員變量來表示同步狀態(tài)津滞,通過內置的FIFO隊列來完成資源獲取線程的排隊工作。

簡單來說:AQS就是一種實現同步狀態(tài)的基礎框架灼伤,我們通過它來實現鎖或者其他同步組件(CountDownLatch触徐、CyclicBarrier)。
仔細看AQS源碼我們發(fā)現其內部維護了一個雙向鏈表狐赡,Node锌介,在Node里面存了前置后置節(jié)點,各種狀態(tài)----CANCEL(取消)猾警、CONDITION孔祸、EXCLUSIVE(獨占)、PROPAGATE(傳播)发皿,總體上都是對隊列的操作崔慧,如下圖:


image.png

在同步器里面維護了一個head(頭部節(jié)點),tail(尾部節(jié)點),每次一個新的線程進來穴墅,都會將當前線程封裝成一個Node節(jié)點加入到尾部節(jié)點惶室,節(jié)點加入同步器的變化如下:


image.png

因為不止一個線程要進行這個操作,所以添加到尾部節(jié)點需要使用循環(huán)的CAS操作來實現玄货;當線程釋放鎖之后皇钞,AQS會將當前節(jié)點的后置節(jié)點設置成首節(jié)點,然后嘗試去獲取狀態(tài)松捉,并將前置節(jié)點設置為空夹界,如果后置節(jié)點為取消狀態(tài),則會從隊列的尾部開始循環(huán)隘世,找到一個最近的沒有被取消的節(jié)點喚醒可柿;


image.png

在看看Condition的隊列實現


image.png

在Condition內部鸠踪,是一個單向鏈表,在同步器里面复斥,維護了首節(jié)點和尾節(jié)點营密,而其他節(jié)點都指向了后面的節(jié)點;再看看Condition同步器和同步隊列之間的關系


image.png

可以看到目锭,我們可以維護多個Condition對象掛載到同步隊列里面评汰,所以每次喚醒的時候,我們只需要喚醒一個就可以了痢虹,使用signal()方法被去,當然,如果是有特殊要求世分,還是需要喚醒當前Condition下面的所有節(jié)點signalAll(),不能跨Condition喚醒缀辩。

了解了AQS的流程之后臭埋,我們需要自己來通過AQS來實現一個自己的鎖。

public class SelfLockDemo implements Lock {
    private static class Sync extends AbstractQueuedSynchronizer {
                //3
        @Override
        protected boolean isHeldExclusively() {
            return getState() > 0;
        }
                //4
        public Condition newCondition() {
            return new ConditionObject();
        }
                //1
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            } else if (getExclusiveOwnerThread() == Thread.currentThread()) {
                setState(getState() + 1);
                return true;
            }
            return false;
        }
                //2
        @Override
        protected boolean tryRelease(int arg) {
            if (getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException();
            }
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setState(getState() - 1);
            if (getState() == 0) {
                setExclusiveOwnerThread(null);
            }
            return true;
        }
    }
    private Sync sync = new Sync();

        //5
    @Override
    public void lock() {
        System.out.println(Thread.currentThread().getName() + " ready get lock");
        sync.acquire(1);
        System.out.println(Thread.currentThread().getName() + " already got lock");
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }
        //6
    @Override
    public void unlock() {
        System.out.println(Thread.currentThread().getName() + " ready release lock");
        sync.release(1);
        System.out.println(Thread.currentThread().getName() + " already released lock");
    }
        //7
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }

上面的1臀玄、2瓢阴、3、4健无、5荣恐、6、7都是我們要實現的方法累贤,上面是實現了一個獨占鎖的例子叠穆,首先實現Lock接口,然后做一個內部類來實現AQS抽象類臼膏,重寫了tryAcquire(int arg) 硼被、tryRelease(int arg)、isHeldExclusively() 方法渗磅,看AQS源碼嚷硫,發(fā)現其內部實現了一些模板方法,所以我們要實現自己的鎖始鱼,需要重寫這些方法仔掸;tryAcquire()方法是去獲取鎖,嘗試用一次CAS操作改變狀態(tài)医清,如果成功起暮,就將當前線程設置成獨占模式,如果不成功会烙,查看當前線程是不是和獨占模式的線程是同一個(可重入的實現)鞋怀,如果都不是双泪,則會調用AQS內部的方法,將當前線程加入同步隊列的尾部密似,阻塞焙矛,等待鎖釋放之后喚醒;tryRelease 方法是 釋放鎖残腌,里面沒有任何CAS操作村斟,是因為需要先判斷是不是拿鎖的線程,如果是抛猫,就對自己進行操作蟆盹,如果狀態(tài)為0,就釋放鎖闺金;isHeldExclusively 判斷線程是否被占用逾滥,這個實現很簡單,就是看同步狀態(tài)败匹;newCondition 這個就類似JAVA提供的wait()寨昙、notify()的實現,其內部維護的是一個Condition同步隊列掀亩,當調用await()方法時候舔哪,當前線程釋放鎖,然后進入阻塞狀態(tài)槽棍;當被signal()方法喚醒時捉蚤,繼續(xù)去獲取鎖,然后執(zhí)行下一步操作炼七;5缆巧、6、7豌拙、8就很簡單了盅蝗,調用AQS內部的方法來實現的;

下面再寫一個實現共享鎖的實現

public class TrinityLockDemo implements Lock {
    private Sync sync = new Sync(3);
    private static class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
            if (count <= 0) {
                throw new IllegalMonitorStateException();
            }
            setState(count);
        }
        @Override
        protected int tryAcquireShared(int arg) {
            for (;;) {
                int current = getState();
                int newCount = current - arg;
                if (newCount < 0 || compareAndSetState(current, newCount)) {
                    return newCount;
                }
            }
        }
        @Override
        protected boolean tryReleaseShared(int arg) {
            for (;;) {
                int current = getState();
                int newCount = current + arg;
                if (compareAndSetState(current, newCount)) {
                    return true;
                }
            }
        }
        final Condition newCondition() {
            return new ConditionObject();
        }
    }
    @Override
    public void lock() {
        sync.acquireShared(1);
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    @Override
    public boolean tryLock() {
        return sync.tryAcquireShared(1) >= 0;
    }
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
    }
    @Override
    public void unlock() {
        sync.releaseShared(1);
    }
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}

共享鎖的區(qū)別就是實現的方法不同姆蘸,tryAcquireShared墩莫、tryReleaseShared,這個類容許一定數量的線程獲取鎖逞敷。而這些方法內部的實現都是循環(huán)調用CAS來操作的狂秦;和獨占鎖有所不同,是因為AQS源碼里面推捐,對獨占鎖的內部實現里面是循環(huán)調用CAS來操作的裂问;

下面來說下JAVA實現的ReentrantLock(里面包含的公平鎖和非公平鎖)ReentrantReadWriteLock,讀寫鎖的實現;
第一個:在AQS源碼里面堪簿,是沒有公平和非公平概念的痊乾。公平鎖的概念是 先等待的線程先獲取到鎖,所以要實現公平鎖椭更,就需要在tryAcquire方法里面哪审,對每一個線程都需要檢查是否在隊列里面,如果不在虑瀑,需要將后來的線程加入到隊列內部來實現公平湿滓;而對于非公平鎖,先去嘗試獲取一次鎖舌狗,如果獲取到了叽奥,就直接使用,獲取不到痛侍,就進入隊列繼續(xù)進行朝氓;
第二個:讀寫鎖
因為AQS只提供了一個int成員變量來同步狀態(tài),而讀寫鎖有兩個鎖主届,需要兩種狀態(tài)赵哲,所以JDK里面就用int 32位的高16位來代表讀鎖,低16位代表寫鎖岂膳;但是有個問題誓竿,讀鎖可以同時有多個線程進入磅网,而int 的 16位不能代表各個線程進入的次數的谈截,這里就引入了ThreadLocal線程本地變量,每個線程獲得讀鎖之后涧偷,就在ThreadLocal里面維護數據簸喂,這樣保證了讀鎖的正確計算;而對于寫鎖燎潮,因為是獨占的喻鳄,所以每次只有一個線程可以獲取,直接進行位運算就可以了确封。
至于鎖的升降級除呵,寫鎖可以退化為讀鎖,而讀鎖不能升級為寫鎖爪喘。因為寫鎖的操作需要對所有讀鎖可見颜曾,如果讀鎖升級為寫鎖,那么某一數據被更改之后秉剑,是不能及時通知其他線程的泛豪,所以 讀寫鎖只能支持寫鎖降級,不支持讀鎖升級。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末诡曙,一起剝皮案震驚了整個濱河市臀叙,隨后出現的幾起案子,更是在濱河造成了極大的恐慌价卤,老刑警劉巖劝萤,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異荠雕,居然都是意外死亡稳其,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門炸卑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來既鞠,“玉大人,你說我怎么就攤上這事盖文≈龅埃” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵五续,是天一觀的道長洒敏。 經常有香客問我,道長疙驾,這世上最難降的妖魔是什么凶伙? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮它碎,結果婚禮上函荣,老公的妹妹穿的比我還像新娘。我一直安慰自己扳肛,他們只是感情好傻挂,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著挖息,像睡著了一般金拒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上套腹,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天绪抛,我揣著相機與錄音,去河邊找鬼电禀。 笑死幢码,一個胖子當著我的面吹牛,可吹牛的內容都是我干的鞭呕。 我是一名探鬼主播蛤育,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼宛官,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瓦糕?” 一聲冷哼從身側響起底洗,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咕娄,沒想到半個月后亥揖,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡圣勒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年费变,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片圣贸。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡挚歧,死狀恐怖,靈堂內的尸體忽然破棺而出吁峻,到底是詐尸還是另有隱情滑负,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布用含,位于F島的核電站矮慕,受9級特大地震影響,放射性物質發(fā)生泄漏啄骇。R本人自食惡果不足惜痴鳄,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缸夹。 院中可真熱鬧痪寻,春花似錦、人聲如沸明未。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趟妥。三九已至,卻和暖如春佣蓉,著一層夾襖步出監(jiān)牢的瞬間披摄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工勇凭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留疚膊,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓虾标,卻偏偏與公主長得像寓盗,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359