參考資料:《Java高并發(fā)程序設(shè)計(jì)》
1.同步控制
1.擴(kuò)展了synchronized功能的:重入鎖
1.簡介
- 使用示例:
import java.util.concurrent.locks.ReentrantLock;
public class Test {
// 聲明鎖
private static ReentrantLock lock = new ReentrantLock();
private static int i = 0;
private static final Runnable runnable = () -> {
for (int j = 0; j < 100000; j++) {
// 加鎖
lock.lock();
try {
i++;
} finally {
// 釋放鎖
lock.unlock();
}
}
};
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
- 重入鎖可以完全替代synchronized關(guān)鍵字。
- 與synchronized相比,重入鎖有著明顯的操作過程。開發(fā)人員必須手動(dòng)指定何時(shí)加鎖哗总,何時(shí)釋放鎖琳轿。也正因?yàn)槿绱嘶Ы模厝腈i對邏輯控制的
靈活性
要遠(yuǎn)好于synchronized剔猿。 - 需要注意的是,在退出臨界區(qū)時(shí),
必須記得釋放鎖
入蛆,否則响蓉,其他線程就沒有機(jī)會再訪問臨界區(qū)了。 - 重入鎖之所以有重入兩字安寺,是因?yàn)?code>這種鎖是可以被同一個(gè)線程反復(fù)進(jìn)入的厕妖。示例中的核心代碼可寫成下面的形式:
lock.lock();
lock.lock();
try {
i++;
} finally {
lock.unlock();
lock.unlock();
}
- 可重入的目的是為了避免同一個(gè)線程在第2次獲取鎖的時(shí)候和自己產(chǎn)生死鎖。
- 需要注意的是:如果一個(gè)線程多次獲得鎖挑庶,那么在釋放鎖的時(shí)候言秸,也必須釋放相同的次數(shù)。
- 如果釋放鎖的次數(shù)多迎捺,那么會得到一個(gè)IllegalMonitorStateException異常举畸。
- 如果加鎖的次數(shù)多,那么相當(dāng)于線程還持有這個(gè)鎖凳枝,其他線程仍無法進(jìn)入臨界區(qū)抄沮。
2.高級功能
1.中斷
- 對于synchronized來說,如果一個(gè)線程在等待鎖岖瑰,那么結(jié)果只有兩種情況:
- 獲得這把鎖繼續(xù)執(zhí)行
- 保持等待
- 而重入鎖提供了另外一種可能:線程可以被
中斷
叛买。即在等待鎖的過程中,程序可以根據(jù)需要取消對鎖的請求蹋订。 - 下面的代碼產(chǎn)生了一個(gè)死鎖率挣,但得益于鎖中斷,可以輕易地解決這個(gè)死鎖:
import java.util.concurrent.locks.ReentrantLock;
import lombok.AllArgsConstructor;
public class Test {
private static final ReentrantLock lock1 = new ReentrantLock();
private static final ReentrantLock lock2 = new ReentrantLock();
@AllArgsConstructor
private static class IntLock implements Runnable {
private int lock;
@Override
public void run() {
try {
if (lock == 1) {
lock1.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("interrupted in sleep");
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("interrupted in sleep");
}
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
System.out.println("interrupted in business");
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":線程退出");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new IntLock(1));
Thread t2 = new Thread(new IntLock(2));
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
// interrupted in business
// 12:線程退出
// 11:線程退出
- 上述代碼中對鎖的請求露戒,統(tǒng)一使用lockInterruptibly()方法椒功。這是一個(gè)可以對中斷進(jìn)行響應(yīng)的鎖申請動(dòng)作,即在等待鎖的過程中智什,可以響應(yīng)線程中斷动漾。
2.鎖申請等待限時(shí)
- 可以使用tryLock()方法進(jìn)行一次限時(shí)的鎖申請:
public class Test {
public static class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(6000);
} else {
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
public static void main(String[] args) {
TimeLock lock = new TimeLock();
Thread t1 = new Thread(lock);
Thread t2 = new Thread(lock);
t1.start();
t2.start();
}
}
// get lock failed
- 上述代碼中的tryLock()方法接收兩個(gè)參數(shù),一個(gè)表示等待時(shí)長荠锭,另一個(gè)表示計(jì)時(shí)單位旱眯。如果申請鎖成功,則返回true证九,否則返回false键思。
- tryLock()方法也可以不帶參數(shù)直接運(yùn)行。這種情況下甫贯,如果鎖未被其他線程占用,則申請鎖成功看蚜,返回true叫搁,否則不會做任何等待,立即返回false。下面演示了這種用法:
public class Test {
public static class TryLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public TryLock(int lock) {
this.lock = lock;
}
public void tryDoubleLock(ReentrantLock firstLock,
ReentrantLock secondLock,
String param, Consumer<String> consumer) {
while (true) {
if (firstLock.tryLock()) {
try {
if (secondLock.tryLock()) {
try {
consumer.accept(param);
return;
} finally {
secondLock.unlock();
}
}
} finally {
firstLock.unlock();
}
}
}
}
@Override
public void run() {
String param = ":My Job done";
Consumer<String> consumer = str ->
System.out.println(Thread.currentThread().getId() + str);
if (lock == 1) {
tryDoubleLock(lock1, lock2, param, consumer);
} else {
tryDoubleLock(lock2, lock1, param, consumer);
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new TryLock(1));
Thread t2 = new Thread(new TryLock(2));
t1.start();
t2.start();
}
}
// 11:My Job done
// 10:My Job done
- 先讓t1獲得lock1渴逻,再讓t2獲得lock2疾党,接著讓t1申請lock2,t2申請lock1惨奕。在一般情況下雪位,這會導(dǎo)致t1和t2相互等待,從而引起死鎖梨撞。但因?yàn)槭褂昧藅ryLock()雹洗,線程不會傻傻等待,而是不斷重試卧波,直到某個(gè)線程同時(shí)獲得lock1和lock2兩把鎖时肿。執(zhí)行上述代碼,可以發(fā)現(xiàn)兩個(gè)線程可以很快雙雙正常執(zhí)行完畢港粱。
3.公平鎖
- 大多數(shù)情況下螃成,鎖的申請都是非公平的。舉個(gè)例子:線程t1首先請求了鎖A查坪,接著線程t2也請求了鎖A寸宏,那么鎖A可用時(shí),是t1獲得鎖還是t2獲得鎖是不確定的偿曙。系統(tǒng)會從這個(gè)鎖的等待隊(duì)列中隨機(jī)挑選一個(gè)氮凝,因此不能保證公平性,有可能會產(chǎn)生饑餓遥昧。
- synchronized關(guān)鍵字進(jìn)行鎖控制所產(chǎn)生的鎖就是非公平的覆醇。
- 重入鎖允許通過構(gòu)造函數(shù)對鎖的公平性進(jìn)行設(shè)置。fair為true表示公平炭臭。
public ReentrantLock(boolean fair)
- 公平鎖的實(shí)現(xiàn)需要維護(hù)一個(gè)有序隊(duì)列永脓,因此鎖的實(shí)現(xiàn)成本高,性能也非常底下鞋仍。因此默認(rèn)情況下鎖是非公平的常摧。如果不是特殊的需求,也不需要公平鎖威创。下面的代碼演示了公平鎖的使用:
public class Test {
public static class FairLock implements Runnable {
public static ReentrantLock fairLock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + "獲得鎖");
} finally {
fairLock.unlock();
}
}
}
}
public static void main(String[] args) {
FairLock r1 = new FairLock();
Thread t1 = new Thread(r1, "Thread_t1");
Thread t2 = new Thread(r1, "Thread_t2");
t1.start();
t2.start();
}
}
// 程序運(yùn)行一段時(shí)間(穩(wěn)定后)的部分輸出:
// Thread_t1獲得鎖
// Thread_t2獲得鎖
// Thread_t1獲得鎖
// Thread_t2獲得鎖
// Thread_t1獲得鎖
// Thread_t2獲得鎖
// Thread_t1獲得鎖
// Thread_t2獲得鎖
- 而如果把fairLock初始化時(shí)的參數(shù)改為false落午,那么根據(jù)系統(tǒng)的調(diào)度,一個(gè)線程會傾向于再次獲取已經(jīng)持有的鎖肚豺,這種分配方式是高效的溃斋,但無公平性可言。
3.總結(jié)
- 對ReentrantLock的幾個(gè)重要方法整理如下:
- lock(): 獲得鎖吸申,如果鎖已經(jīng)被占用梗劫,則等待享甸。
- lockInterruptibly(): 獲得鎖,但優(yōu)先響應(yīng)中斷梳侨。
- tryLock(): 嘗試獲得鎖蛉威,如果成功返回true,失敗返回false走哺。該方法不等待蚯嫌,立即返回。
- tryLock(long time, TimeUnit unit): 在給定的時(shí)間內(nèi)嘗試獲得鎖丙躏。
- unlock(): 釋放鎖择示。
- 從重入鎖的實(shí)現(xiàn)來看,主要包含三個(gè)要素:
- 原子狀態(tài)彼哼。原子狀態(tài)使用CAS操作(CompareAndSwap)來存儲當(dāng)前鎖的狀態(tài)对妄,判斷鎖是否已經(jīng)被別的線程持有。
- 等待隊(duì)列敢朱。所有沒有請求到鎖的線程剪菱,會進(jìn)入等待隊(duì)列進(jìn)行等待。待有線程釋放鎖后拴签,系統(tǒng)就能從等待隊(duì)列中喚醒一個(gè)線程孝常,繼續(xù)工作。
- 阻塞原語park()和unpark()蚓哩。這兩個(gè)方法用來掛起和恢復(fù)線程构灸。沒有得到鎖的線程將會被掛起。有關(guān)park()和unpark()可參考線程阻塞工具類LockSupport岸梨。
2.重入鎖的好搭檔:Condition條件
1.簡介
- Condition的作用和Object.wait()喜颁、Object.notify()的作用是大致相同的。但是Condition和重入鎖相關(guān)聯(lián)曹阔,而后者是和synchronized關(guān)鍵字合作使用的半开。
- 通過Lock接口(重入鎖就實(shí)現(xiàn)了該接口)的newCondition()方法可以生成一個(gè)與當(dāng)前重入鎖綁定的Condition實(shí)例。利用Condition實(shí)例就可以讓線程在合適的時(shí)間等待赃份,或在特定的時(shí)刻得到通知繼續(xù)執(zhí)行寂拆。
2.方法
await()
調(diào)用該方法的前提是,當(dāng)前線程已經(jīng)成功獲得與該條件對象綁定的重入鎖抓韩,否則調(diào)用該方法時(shí)會拋出IllegalMonitorStateException纠永。調(diào)用該方法外,當(dāng)前線程會釋放當(dāng)前已經(jīng)獲得的鎖(這一點(diǎn)與Object.wait方法一致)谒拴,并且等待其它線程調(diào)用該條件對象的signal()或者signalAll()方法(這一點(diǎn)與Object.notify()或Object.notifyAll()很像)尝江。或者在等待期間英上,當(dāng)前線程被中斷茂装,則wait()方法會拋出InterruptedException并清除當(dāng)前線程的中斷狀態(tài)怠蹂。await(long time, TimeUnit unit)
適用條件和行為與await()基本一致,唯一不同點(diǎn)在于少态,指定時(shí)間之內(nèi)沒有收到signal()或signalALL()信號或者線程中斷時(shí)該方法會返回false;其它情況返回true。awaitNanos(long nanosTimeout)
調(diào)用該方法的前提是易遣,當(dāng)前線程已經(jīng)成功獲得與該條件對象綁定的重入鎖彼妻,否則調(diào)用該方法時(shí)會拋出IllegalMonitorStateException。nanosTimeout指定該方法等待信號的的最大時(shí)間(單位為納秒)豆茫。若指定時(shí)間內(nèi)收到signal()或signalALL()則返回nanosTimeout減去已經(jīng)等待的時(shí)間侨歉;若指定時(shí)間內(nèi)有其它線程中斷該線程,則拋出InterruptedException并清除當(dāng)前線程的中斷狀態(tài)揩魂;若指定時(shí)間內(nèi)未收到通知幽邓,則返回0或負(fù)數(shù)。awaitUntil(Date deadline)
適用條件與行為與awaitNanos(long nanosTimeout)完全一樣火脉,唯一不同點(diǎn)在于它不是等待指定時(shí)間牵舵,而是等待由參數(shù)指定的某一時(shí)刻。awaitUninterruptibly()
調(diào)用該方法的前提是倦挂,當(dāng)前線程已經(jīng)成功獲得與該條件對象綁定的重入鎖畸颅,否則調(diào)用該方法時(shí)會拋出IllegalMonitorStateException。調(diào)用該方法后方援,結(jié)束等待的唯一方法是其它線程調(diào)用該條件對象的signal()或signalALL()方法没炒。等待過程中如果當(dāng)前線程被中斷,該方法仍然會繼續(xù)等待犯戏,同時(shí)保留該線程的中斷狀態(tài)送火。signal()
用于喚醒一個(gè)等待中的線程。類似于Object.notify()signalAll()
會喚醒所有在等待中的線程先匪。類似于Object.notifyAll()代碼演示:
public class Test {
public static class ReenterLockCondition implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock(); // 申請并獲得鎖
condition.await(); // 釋放鎖
// 收到signal信號后重新申請鎖种吸、獲得鎖
System.out.println("thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 釋放鎖
}
}
}
public static void main(String[] args) throws Exception {
new Thread(new ReenterLockCondition()).start();
Thread.sleep(2000);
ReenterLockCondition.lock.lock(); // 獲得鎖
ReenterLockCondition.condition.signal(); // signal通知
ReenterLockCondition.lock.unlock(); // 釋放鎖,謙讓給被喚醒的線程
}
}
// thread is going on
3.允許多個(gè)線程同時(shí)訪問:信號量(Semaphore)
1.簡介
- 從廣義上說胚鸯,信號量是對鎖的擴(kuò)展骨稿。無論是內(nèi)部鎖synchronized還是重入鎖ReentrantLock,一次都只允許一個(gè)線程訪問一個(gè)資源姜钳,而信號量卻可以指定多個(gè)線程坦冠,同時(shí)訪問某一個(gè)資源。
- 信號量主要提供了以下構(gòu)造函數(shù)哥桥。在構(gòu)造信號量對象時(shí)辙浑,必須指定信號量的準(zhǔn)入數(shù),即同時(shí)能申請多少個(gè)許可拟糕。當(dāng)每個(gè)線程每次只申請一個(gè)許可時(shí)判呕,這就相當(dāng)于指定了同時(shí)有多少個(gè)線程可以訪問某一個(gè)資源倦踢。
public Semaphore(int permits) // 參數(shù)為信號量的準(zhǔn)入數(shù)
public Semaphore(int permits, boolean fair) // 第二個(gè)參數(shù)可以指定是否公平
2.方法
acquire()
嘗試獲得一個(gè)準(zhǔn)入的許可。若無法獲得侠草,則線程會等待辱挥,直到有線程釋放一個(gè)許可或者當(dāng)前線程被中斷。acquireUninterruptibly()
和acquire()方法類似边涕,但是不響應(yīng)中斷晤碘。tryAcquire()
嘗試獲得一個(gè)許可,如果成功返回true功蜓,失敗返回false园爷,不會等待,立即返回式撼。release()
用于在線程訪問資源結(jié)束后童社,釋放一個(gè)許可,以使其他等待許可的線程可以進(jìn)行資源訪問著隆。代碼演示:
public class Test {
public static class SemaphoreDemo implements Runnable {
final Semaphore semaphore = new Semaphore(5);
@Override
public void run() {
try {
semaphore.acquire();
Thread.sleep(2000);
System.out.println(System.currentTimeMillis() + "-" +
Thread.currentThread().getId() + ":done!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}
}
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newFixedThreadPool(20);
final SemaphoreDemo demo = new SemaphoreDemo();
for (int i = 0; i < 20; i++) {
exec.submit(demo);
}
}
}
// 從輸出可以觀察出系統(tǒng)以5個(gè)線程一組為單位扰楼,依次進(jìn)行輸出
4.讀寫分離鎖:ReadWriteLock
- 讀寫分離鎖(簡稱“讀寫鎖”)可以有效地幫助 減少鎖競爭,以提升系統(tǒng)性能旅东。
- 讀寫鎖允許多個(gè)線程同時(shí)讀灭抑,但考慮到數(shù)據(jù)完整性,寫寫操作和讀寫操作間依然是需要相互等待和持有鎖的抵代。讀寫鎖的訪問約束如下表:
- | 讀 | 寫 |
---|---|---|
讀 | 非阻塞 | 阻塞 |
寫 | 阻塞 | 阻塞 |
- 在系統(tǒng)中腾节,如果讀操作次數(shù)遠(yuǎn)大于寫操作,那么讀寫鎖的功效就會非常明顯荤牍。例如下面的代碼:
public class Test {
private static Lock lock = new ReentrantLock();
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private static CountDownLatch latch = new CountDownLatch(20);
private int value;
public Object handleRead(Lock lock) throws InterruptedException {
try {
lock.lock(); // 模擬讀操作
Thread.sleep(1000); // 讀操作的耗時(shí)越多案腺,讀寫鎖的優(yōu)勢就越明顯
return value;
} finally {
latch.countDown();
lock.unlock();
}
}
public void handleWrite(Lock lock, int index) throws InterruptedException {
try {
lock.lock(); // 模擬寫操作
Thread.sleep(1000);
value = index;
} finally {
latch.countDown();
lock.unlock();
}
}
public static void main(String[] args) throws Exception {
final Test demo = new Test();
Runnable readRunnable = () -> {
try {
demo.handleRead(readLock);
//demo.handleRead(lock); // 使用重入鎖而不是讀寫鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Runnable writeRunnable = () -> {
try {
demo.handleWrite(writeLock, new Random().nextInt());
//demo.handleRead(lock); // 使用重入鎖而不是讀寫鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
};
long startTime = System.currentTimeMillis();
for (int i = 0; i <= 18; i++) {
new Thread(readRunnable).start();
}
for (int i = 19; i < 20; i++) {
new Thread(writeRunnable).start();
}
latch.await();
System.out.println("used time: " + (System.currentTimeMillis() - startTime));
}
}
// 使用讀寫鎖
// used time: 2022
// 使用重入鎖
// used time: 20009
- 通過運(yùn)行結(jié)果可以看出,使用讀寫鎖程序大約2秒多就能結(jié)束(寫線程直接是實(shí)際串行的)康吵。而使用重入鎖劈榨,所有的讀和寫線程之間都必須相互等待,整個(gè)程序的執(zhí)行時(shí)間將長達(dá)20余秒晦嵌。
5.倒計(jì)數(shù)器:CountDownLatch
倒計(jì)數(shù)器通常用來控制線程等待同辣,它可以讓某一個(gè)線程等待直到倒計(jì)數(shù)結(jié)束,再執(zhí)行惭载。
-
上一節(jié)介紹讀寫鎖統(tǒng)計(jì)用時(shí)時(shí)旱函,就是使用的倒計(jì)數(shù)器。此外倒計(jì)數(shù)器的一種典型場景就是火箭發(fā)射描滔。在火箭發(fā)射前棒妨,為確保萬無一失,往往會進(jìn)行各項(xiàng)設(shè)備儀器的檢查含长。只有等所有的檢查完畢后券腔,引擎才能點(diǎn)火伏穆。這種場景就非常適合使用CountDownLatch。它可以使點(diǎn)火線程等待所有檢查線程全部完工后纷纫,再執(zhí)行枕扫。示意圖如下:
倒計(jì)時(shí)器的幾個(gè)關(guān)鍵方法
// 構(gòu)造函數(shù)接收一個(gè)整數(shù)作為參數(shù),即當(dāng)前這個(gè)倒計(jì)時(shí)器的計(jì)數(shù)個(gè)數(shù)
public CountDownLatch(int count)
// 計(jì)數(shù)減一
public void countDown()
// 使線程等待涛酗,直到倒計(jì)數(shù)完成
public void await()
6.循環(huán)柵欄:CyclicBarrier
- CyclicBarrier與CountDownLatch類似铡原,也可實(shí)現(xiàn)線程間的 計(jì)數(shù)等待 ,但功能更加強(qiáng)大:這個(gè)計(jì)數(shù)器可以 反復(fù)使用商叹。
- 循環(huán)柵欄的一個(gè)使用場景:比如司令下達(dá)命令,要求10個(gè)士兵一起去完成一項(xiàng)任務(wù)只泼。這時(shí)剖笙,就會要求10個(gè)士兵先集合報(bào)道,全部報(bào)道完畢后(第一次計(jì)數(shù)完成)请唱,再一起去執(zhí)行任務(wù)弥咪,當(dāng)10個(gè)士兵把自己的任務(wù)都完成了(第二次計(jì)數(shù)完成),那么司令再對外宣布:任務(wù)完成十绑!
- CyclicBarrier有兩個(gè)主要的方法:
// parties:計(jì)數(shù)總數(shù)聚至,也就是參與的線程總數(shù)
// barrierAction:當(dāng)計(jì)數(shù)器完成一次計(jì)數(shù)后,系統(tǒng)要執(zhí)行的動(dòng)作
public CyclicBarrier(int parties, Runnable barrierAction)
// 等待一次計(jì)數(shù)完成
// 注意拋出的兩種異常(見下一節(jié)的代碼)
public int await() throws InterruptedException, BrokenBarrierException
- 上面的的場景用代碼實(shí)現(xiàn)如下:
public class Test {
private static final int SOLDIER_NUM = 10;
private volatile static boolean allAssembledFlag = false;
private static final Runnable barrierRun = () -> {
if (allAssembledFlag) {
System.out.println("司令:[士兵" + SOLDIER_NUM + "個(gè)本橙,任務(wù)完成扳躬!]");
} else {
System.out.println("司令:[士兵" + SOLDIER_NUM + "個(gè),集合完畢甚亭!]");
}
};
public static class Soldier implements Runnable {
private final CyclicBarrier cyclic;
private String soldierName;
Soldier(CyclicBarrier cyclic, String soldierName) {
this.cyclic = cyclic;
this.soldierName = soldierName;
}
void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt() % 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldierName + ":任務(wù)完成");
}
@Override
public void run() {
try {
// 等待所有士兵到齊
cyclic.await();
allAssembledFlag = true;
doWork();
// 等待所有士兵完成工作
cyclic.await();
} catch (InterruptedException e) {
System.out.println("線程等待時(shí)被中斷——需要執(zhí)行響應(yīng)外部緊急事件的邏輯");
} catch (BrokenBarrierException e) {
System.out.println("當(dāng)前的CyclicBarrier已經(jīng)被損壞," +
"系統(tǒng)可能已經(jīng)無法等待所有線程到齊——需要執(zhí)行使等待線程就地解散的邏輯");
// 例如當(dāng)前循環(huán)柵欄的計(jì)數(shù)總數(shù)為10,若有一個(gè)線程被中斷,
// 那我們就會得到1個(gè)InterruptedException和9個(gè)BrokenBarrierException
}
}
}
public static void main(String[] args) throws Exception {
Thread[] allSoldier = new Thread[SOLDIER_NUM];
CyclicBarrier cyclic = new CyclicBarrier(SOLDIER_NUM, barrierRun);
// 設(shè)置屏障點(diǎn)贷币,主要是為了執(zhí)行這個(gè)方法
System.out.println("集合隊(duì)伍!");
for (int i = 0; i < SOLDIER_NUM; ++i) {
System.out.println("士兵 " + i + " 報(bào)道亏狰!");
allSoldier[i] = new Thread(new Soldier(cyclic, "士兵 " + i));
allSoldier[i].start();
}
}
}
// 集合隊(duì)伍役纹!
// 士兵 0 報(bào)道!
// 士兵 1 報(bào)道暇唾!
// 士兵 2 報(bào)道促脉!
// 士兵 3 報(bào)道!
// 士兵 4 報(bào)道策州!
// 士兵 5 報(bào)道瘸味!
// 士兵 6 報(bào)道!
// 士兵 7 報(bào)道抽活!
// 士兵 8 報(bào)道硫戈!
// 士兵 9 報(bào)道!
// 司令:[士兵10個(gè)下硕,集合完畢丁逝!]
// 士兵 3:任務(wù)完成
// 士兵 5:任務(wù)完成
// 士兵 9:任務(wù)完成
// 士兵 7:任務(wù)完成
// 士兵 4:任務(wù)完成
// 士兵 1:任務(wù)完成
// 士兵 8:任務(wù)完成
// 士兵 0:任務(wù)完成
// 士兵 6:任務(wù)完成
// 士兵 2:任務(wù)完成
// 司令:[士兵10個(gè)汁胆,任務(wù)完成!]
7.線程阻塞工具類:LockSupport
- LockSupport是一個(gè)線程阻塞工具霜幼,可以在線程內(nèi)任意位置讓線程阻塞嫩码。
- 和Thread.suspend()相比,它彌補(bǔ)了由于resume()在前發(fā)生罪既,導(dǎo)致線程無法繼續(xù)執(zhí)行的情況铸题。
- 和Object.wait()相比,它不需要先獲得某個(gè)對象的鎖琢感,也不會拋出InterruptedException異常丢间。
- LockSupport的 靜態(tài)方法park() 可以阻塞當(dāng)前線程,類似的還有parkNanos()驹针、parkUntil()等方法烘挫,它們實(shí)現(xiàn)了一個(gè)限時(shí)的等待。
- 之前提到的有關(guān) suspend()永久卡死線程 的例子柬甥,可用LockSupport重寫如下:
public class Test {
private static final Object u = new Object();
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (u) {
try {
System.out.println("in " + getName());
Thread.sleep(1000);
LockSupport.park();
System.out.println("out " + getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
ChangeObjectThread t1 = new ChangeObjectThread("t1");
ChangeObjectThread t2 = new ChangeObjectThread("t2");
t1.start();
t2.start();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}
// in t1
// out t1
// in t2
// out t2
這里只是將原來的suspend()和resume()方法用park()和unpark()方法做了替換饮六。并且為了確保unpark()在park()之前被調(diào)用,還讓線程多sleep了1秒苛蒲。但執(zhí)行這段代碼可以發(fā)現(xiàn)卤橄,它永遠(yuǎn)可以正常的結(jié)束,不會被永久性的掛起臂外。
LockSupport不會因unpark()先于park()執(zhí)行而被永久性掛起的原因窟扑,是因?yàn)樗褂昧祟愃?信號量 的機(jī)制。它為每一個(gè)線程準(zhǔn)備了一個(gè) 許可寄月,如果許可 可用辜膝,那么park()函數(shù)會立即返回,并且 消費(fèi)掉 這個(gè)許可(也就是將許可變?yōu)?不可用)漾肮,如果許可不可用厂抖,就會阻塞。而unpark()則可以使一個(gè)許可變?yōu)榭捎茫ǖ托盘柫坎煌氖强税茫S可不能累加忱辅,你不可能擁有超過一個(gè)許可,它永遠(yuǎn)只有一個(gè))谭溉。
-
此外墙懂,處于park()掛起狀態(tài)的線程不會像suspend()那樣給出一個(gè)令人費(fèi)解的Runnable的狀態(tài)。它會非常明確地給出一個(gè)WAITING狀態(tài)扮念,而且還會標(biāo)注是park()引起的:
-
這種標(biāo)注使得分析問題非常方便损搬。此外,如果使用park(Object)函數(shù),還可以為當(dāng)前線程設(shè)置一個(gè)阻塞對象巧勤。這個(gè)阻塞對象會出現(xiàn)在線程Dump中嵌灰,這樣分析問題就更方便了。例如將上述代碼中的park()改為park(this)颅悉,那么在線程Dump中沽瞭,可以看到如下信息:
除了有定時(shí)阻塞的功能外,LockSupport.park()還支持中斷響應(yīng)剩瓶。但和其他接收中斷的函數(shù)不同驹溃,park()函數(shù)不會拋出InterruptedException異常。park()函數(shù)只會默默的返回延曙,但我們可以從Thread.interrupted()等方法獲得中斷標(biāo)記豌鹤。例如:
public class Test {
private static final Object u = new Object();
public static class ChangeObjectThread extends Thread {
public ChangeObjectThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (u) {
System.out.println("in " + getName());
LockSupport.park();
if (Thread.interrupted()) {
System.out.println(getName() + " 被中斷了");
}
System.out.println("out " + getName());
}
}
}
public static void main(String[] args) throws Exception {
ChangeObjectThread t1 = new ChangeObjectThread("t1");
ChangeObjectThread t2 = new ChangeObjectThread("t2");
t1.start();
Thread.sleep(100);
t2.start();
t1.interrupt();
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}
// in t1
// t1 被中斷了
// out t1
// in t2
// out t2
end