Java高并發(fā)--AQS

Java高并發(fā)--AQS

主要是學(xué)習(xí)慕課網(wǎng)實(shí)戰(zhàn)視頻《Java并發(fā)編程入門與高并發(fā)面試》的筆記

AQS是AbstractQueuedSynchronizer的簡稱忠荞,直譯過來是抽象隊(duì)列同步器。AQS的底層數(shù)據(jù)結(jié)構(gòu)是隊(duì)列岩馍,如下所示

image

AQS使用Node實(shí)現(xiàn)FIFO隊(duì)列,可以用于構(gòu)建鎖或者其他同步裝置的基礎(chǔ)框架薄榛,利用一個(gè)int類型表示狀態(tài)(state)

使用該框架的功能需要讓子類繼承岔激,并重寫相關(guān)方法。

  • 子類通過繼承并通過重寫方法管理其狀態(tài)acquire和release的方法操縱狀態(tài)

  • 可以同時(shí)實(shí)現(xiàn)排他鎖和共享鎖模式(獨(dú)占遇八、共享)矛绘,要么使用獨(dú)占鎖要么使用共享鎖,而不會同時(shí)使用兩者

閉鎖- CountdownLatch

常稱為"閉鎖"刃永,是一個(gè)倒計(jì)數(shù)器货矮,如下,設(shè)定了倒計(jì)數(shù)值為3.當(dāng)前線程調(diào)用await()被阻塞斯够,其他線程每調(diào)用一次countDown()計(jì)數(shù)器減1囚玫,一直到計(jì)數(shù)值cnt等于0時(shí)喧锦,當(dāng)前線程才可以繼續(xù)(恢復(fù))執(zhí)行∽ザ剑可以理解為一個(gè)線程等待多個(gè)線程執(zhí)行完畢裸违,這樣該線程可以利用其他多個(gè)線程的執(zhí)行結(jié)果。

image

下面是CountdownLatch的簡單使用本昏,當(dāng)計(jì)數(shù)器從60減到0時(shí)候供汛,當(dāng)前線程會打印“Go!”

由于當(dāng)前線程被阻塞,需等到其余60個(gè)線程執(zhí)行完畢涌穆,因此當(dāng)前線程的"Go!"會最后打印怔昨。

package com.shy.concurrency.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author Haiyu
 * @date 2019/1/3 11:00
 */
@Slf4j
public class CountdownLatchExample {
    private static CountDownLatch cdl = new CountDownLatch(60);

    public static void main(String[] args) throws InterruptedException {
        final int num = 60;
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = num; i >= 0; i--) {
            final int cur = i;
            executorService.execute(() -> {
                try {
                    run(cur);
                } catch (InterruptedException e) {
                    log.error("exception", e);
                } finally {
                    cdl.countDown();
                }
            });
        }
        cdl.await();
        System.out.println("Go!");
        executorService.shutdown();
    }

    public static void run(int cur) throws InterruptedException {
        Thread.sleep(100);
        System.out.println(cur);
    }
}

使用CountdownLatch還可以實(shí)現(xiàn)限時(shí)任務(wù),在await()方法中可以傳入?yún)?shù)宿稀,如下表示當(dāng)前線程只會等待10毫秒趁舀,超過這個(gè)時(shí)間就不再等待而是恢復(fù)執(zhí)行。

cdl.await(10, TimeUnit.MICROSECONDS);

將上面的程序cdl.await();修改成cdl.await(10, TimeUnit.MICROSECONDS);祝沸,將最先打印"Go!"因?yàn)樗坏却?0毫秒矮烹,而其他線程每次執(zhí)行前都會sleep100毫秒。

信號量 - Semaphore

可以控制某個(gè)資源可以有多少個(gè)線程同時(shí)訪問罩锐,即可以控制并發(fā)量奉狈。主要通過acquire和release方法,

  • acquire():獲得一個(gè)許可涩惑,若無法獲得會一直等待直到有線程釋放了許可或者被中斷
  • tryAcquire():嘗試獲得一個(gè)許可仁期,獲取成功返回true,失敗返回false竭恬,不會等待立即返回
  • release():釋放一個(gè)許可

acquire()release()還可以傳入?yún)?shù)跛蛋,指定一次獲得/釋放的許可數(shù);tryAcquire()還可以指定時(shí)間痊硕,在指定時(shí)間內(nèi)沒有獲得到許可就返回false赊级。

循環(huán)柵欄 - CyclicBarrier

cyclicBarrier強(qiáng)調(diào)線程之間互相等待

image

CountdownLatch和CyclicBarrier的區(qū)別如下:

  • CountDownLatch強(qiáng)調(diào)一個(gè)線程等待其他所有線程,通過cdl.await()讓當(dāng)前線程等待在倒計(jì)數(shù)器上岔绸,每有一個(gè)線程執(zhí)行完理逊,cdl.countDown(),將計(jì)數(shù)減1亭螟,減到0時(shí)通知當(dāng)前線程執(zhí)行挡鞍。簡單的說就是一個(gè)線程等待,直到他所等待的其他線程都執(zhí)行完成预烙,當(dāng)前線程才可以繼續(xù)執(zhí)行墨微。
  • cyclicBarrier強(qiáng)調(diào)線程之間互相等待,只要有一個(gè)線程還沒到來扁掸,所有線程會一起等待翘县∽钣颍可以傳入一個(gè)Runnable作為計(jì)數(shù)完成要執(zhí)行的任務(wù)。每有一個(gè)線程調(diào)用cyc.await()計(jì)數(shù)減1锈麸,減到0時(shí)會執(zhí)行一次該Runnable镀脂。簡單地說就是線程之間互相等待,等所有線程都準(zhǔn)備好忘伞,即調(diào)用await()方法之后薄翅,執(zhí)行一次Runnable,此時(shí)所有線程開始同時(shí)執(zhí)行氓奈!
  • CountDownLatch的計(jì)數(shù)器只能使用一次翘魄。而CyclicBarrier的計(jì)數(shù)器可以使用reset() 方法重置。

下面是一個(gè)例子舀奶,CyclicBarrier還可以傳入一個(gè)Runnable暑竟, 每次計(jì)數(shù)到的時(shí)候都會執(zhí)行一次。

package com.shy.concurrency.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

/**
 * @author Haiyu
 * @date 2019/1/3 11:00
 */
@Slf4j
public class CyclicBarrierExample {
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(10, () -> System.out.println("集合完畢"));

    public static void main(String[] args) throws InterruptedException {
        final int num = 10;
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0;i < num; i++) {

            final int cur = i;
            executorService.execute(() -> {
                try {
                    run(cur);
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        executorService.shutdown();
    }

    public static void run(int cur) throws InterruptedException, BrokenBarrierException {
        log.info("{} is ready", cur);
        cyclicBarrier.await();
        log.info("{} is working", cur);
    }
}

CyclicBarrier(10, () -> System.out.println("集合完畢"));該句表示10個(gè)線程相互等待育勺,所有10個(gè)線程都調(diào)用了await()方法后但荤,會執(zhí)行一次傳入的Runnable;之后10個(gè)線程被喚醒涧至,計(jì)數(shù)重新開始腹躁,這也是Cyclic的意義——可重復(fù)利用的計(jì)數(shù)器。

因此上面程序會先打印10個(gè)ready化借,然后打印一次“集合完畢”潜慎;之后會打印10個(gè)working。

重入鎖 - ReentrantLock

synchronized是JVM的內(nèi)置鎖蓖康,而重入鎖是Java代碼實(shí)現(xiàn)的。重入鎖是synchronized的擴(kuò)展垒手,可以完全代替后者蒜焊。重入鎖可以重入,允許同一個(gè)線程連續(xù)多次獲得同一把鎖科贬。其次泳梆,重入鎖獨(dú)有的功能有:

  • 可以相應(yīng)中斷,synchronized要么獲得鎖執(zhí)行榜掌,要么保持等待优妙。而重入鎖可以響應(yīng)中斷,使得線程在遲遲得不到鎖的情況下憎账,可以不再等待套硼。主要由lockInterruptibly()實(shí)現(xiàn),這是一個(gè)可以對中斷進(jìn)行響應(yīng)的鎖申請動(dòng)作胞皱,鎖中斷可以避免死鎖邪意。
  • 鎖的申請可以有等待時(shí)限九妈,用tryLock()可以實(shí)現(xiàn)限時(shí)等待,如果超時(shí)還未獲得鎖會返回false雾鬼,也防止了線程遲遲得不到鎖時(shí)一直等待萌朱,可避免死鎖。
  • 公平鎖策菜,即鎖的獲得按照線程先來后到的順序依次獲得晶疼,不會產(chǎn)生饑餓現(xiàn)象。synchronized的鎖默認(rèn)是不公平的又憨,重入鎖可通過傳入構(gòu)造方法的參數(shù)實(shí)現(xiàn)公平鎖翠霍。
  • 重入鎖可以綁定多個(gè)Condition條件,這些condition通過調(diào)用await/singal實(shí)現(xiàn)線程間通信竟块『耍可以實(shí)現(xiàn)分組喚醒需要喚醒的線程,而不是像synchronized的wait/notify一樣要么隨機(jī)喚醒要給要么喚醒全部。

讀寫鎖 - ReentrantReadWriteLock

ReadWriteLock即讀寫鎖浪秘,它有兩個(gè)方法如下蒋情,分別返回一個(gè)讀鎖和寫鎖,即讀寫鎖分離耸携。

ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock();
Lock writeLock = readWriteLock.writeLock();

在讀時(shí)使用readLock進(jìn)行加鎖棵癣,在寫時(shí)使用writeLock進(jìn)行加鎖。使得讀-讀不阻塞夺衍,讀線程完全并行狈谊,適合讀多寫少的場合。讀鎖會完全阻塞寫鎖,它使用的依然是悲觀的鎖策略沟沙。如果有大量的讀線程河劝,也有可能引起寫線程的饑餓。(如果想獲得寫鎖矛紫,不允許還有讀鎖赎瞎、寫鎖還保持著,即 在沒有任何讀鎖颊咬、寫鎖的情況下才能獲得寫鎖)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末务甥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子喳篇,更是在濱河造成了極大的恐慌敞临,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麸澜,死亡現(xiàn)場離奇詭異挺尿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門票髓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來攀涵,“玉大人,你說我怎么就攤上這事洽沟∫怨剩” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵裆操,是天一觀的道長怒详。 經(jīng)常有香客問我,道長踪区,這世上最難降的妖魔是什么昆烁? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮缎岗,結(jié)果婚禮上静尼,老公的妹妹穿的比我還像新娘。我一直安慰自己传泊,他們只是感情好鼠渺,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著眷细,像睡著了一般拦盹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溪椎,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天普舆,我揣著相機(jī)與錄音,去河邊找鬼校读。 笑死沼侣,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歉秫。 我是一名探鬼主播华临,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼端考!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揭厚,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤却特,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后筛圆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裂明,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年太援,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闽晦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扳碍。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖仙蛉,靈堂內(nèi)的尸體忽然破棺而出笋敞,到底是詐尸還是另有隱情,我是刑警寧澤荠瘪,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布夯巷,位于F島的核電站,受9級特大地震影響哀墓,放射性物質(zhì)發(fā)生泄漏趁餐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一篮绰、第九天 我趴在偏房一處隱蔽的房頂上張望后雷。 院中可真熱鬧,春花似錦吠各、人聲如沸臀突。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惧辈。三九已至,卻和暖如春磕瓷,著一層夾襖步出監(jiān)牢的瞬間盒齿,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工困食, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留边翁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓硕盹,卻偏偏與公主長得像符匾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子瘩例,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容