Java高并發(fā)--AQS
主要是學(xué)習(xí)慕課網(wǎng)實(shí)戰(zhàn)視頻《Java并發(fā)編程入門與高并發(fā)面試》的筆記
AQS是AbstractQueuedSynchronizer的簡稱忠荞,直譯過來是抽象隊(duì)列同步器。AQS的底層數(shù)據(jù)結(jié)構(gòu)是隊(duì)列岩馍,如下所示
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é)果。
下面是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)線程之間互相等待
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)行加鎖。使得讀-讀不阻塞夺衍,讀線程完全并行狈谊,適合讀多寫少的場合。讀鎖會完全阻塞寫鎖,它使用的依然是悲觀的鎖策略沟沙。如果有大量的讀線程河劝,也有可能引起寫線程的饑餓。(如果想獲得寫鎖矛紫,不允許還有讀鎖赎瞎、寫鎖還保持著,即 在沒有任何讀鎖颊咬、寫鎖的情況下才能獲得寫鎖)