自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

領(lǐng)導(dǎo)說誰再用Stop直接下崗,這樣終止線程更優(yōu)雅

開發(fā) 前端
即使我們先調(diào)用了unpark,再調(diào)用park也是可以正常喚醒線程的,因?yàn)閡npark獲取了一個許可證,之后再調(diào)用park方法,就可以名正言順的憑證消費(fèi),故不會阻塞。

本文收錄于《Java并發(fā)編程》合集,本文主要介紹Java并發(fā)編程中終止線程的手段,通過本文您可以了解到:

  • 通過Thread類提供的方法中斷線程
  • 中斷線程的應(yīng)用場景和代碼實(shí)現(xiàn),以及實(shí)現(xiàn)中的細(xì)節(jié)處理
  • stop方法中斷線程存在的隱患
  • LockSupport停止和喚醒線程
  • LockSupport工具類的park和unpark的原理

原本的Java線程Thread類API中提供了stop這樣的終止線程的方法,但是已被標(biāo)記為過時方法,此方法來終止線程是暴力的不安全的,沒有對線程做后續(xù)的善后操作而直接終止,往往會埋下一些隱患。我們可以通過Java線程的中斷機(jī)制,來安全的停止線程。

Java提供了線程的中斷機(jī)制:設(shè)置線程的中斷標(biāo)志,可以使用它來決定是否結(jié)束一個線程。通過設(shè)置線程的中斷標(biāo)志并不能直接終止線程,這種機(jī)制其實(shí)就是告知線程我希望打斷你,至于到底停止不停止是由線程決定。

打斷線程場景

比如打斷或者重新執(zhí)行一些耗時過長任務(wù),多線程同時完成同一個相同任務(wù),某一線程如果執(zhí)行完就通知其他線程可以停止。比如:

  • 下載任務(wù),發(fā)現(xiàn)需要耗時過長,可以直接取消下載,其實(shí)就是打斷這個下載數(shù)據(jù)線程
  • 比如搶票軟件,開啟多個線程搶多個車次的車票,如果某一個線程搶到,就通知其他線程可以終止
  • 比如服務(wù)器中的超時操作,長時間沒有獲取到數(shù)據(jù),就終止線程

你在哪里使用過打斷線程呢?

Thread類相關(guān)API

  • void interrupt():中斷線程,例如線程A運(yùn)行時,線程B調(diào)用線程A的interrupt方法來設(shè)置線程A的中斷標(biāo)志為true。注意:這里僅僅是設(shè)置了標(biāo)志,線程A并沒有中斷,它會繼續(xù)往下執(zhí)行。如果線程A調(diào)用了wait,join,sleep方法而被阻塞掛起,如果此時調(diào)用線程A的interrupt()方法,線程A會在調(diào)用這些方法的地方拋出InterruptedException異常而返回。
  • boolean isInterrupted():檢測當(dāng)前線程是否被中斷,如果是返回true,否則返回false。
  • boolean interrupted():檢測當(dāng)前線程是否被中斷,這個方法是Thread類的靜態(tài)方法;與interrupt()方法的區(qū)別在于,如果發(fā)現(xiàn)當(dāng)前線程被中斷,則會清除中斷標(biāo)志。

另外需要注意的是:interrupted()方法是獲取當(dāng)前調(diào)用線程【正在運(yùn)行的線程】的中斷標(biāo)志,而不是調(diào)用interrupted()方法的線程的中斷標(biāo)志。

打斷線程

public class InterruptThread {

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

// 開啟線程
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "運(yùn)行......");
// 線程進(jìn)入睡眠狀態(tài)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1線程");
// 啟動 t1 線程
t1.start();
// 等待1秒后打斷t1線程
Thread.sleep(1000);
System.out.println("打斷......");
// 打斷線程
t1.interrupt();
// 查看打斷標(biāo)記
System.out.println("打斷標(biāo)記:" + t1.isInterrupted());
}
}

運(yùn)行結(jié)果:

  • 調(diào)用 interrupted方法之后如果被打斷的線程【t1】線程中調(diào)用sleep、wait、join方法會觸發(fā)異常
  • 調(diào)用isInterrupted方法獲取打斷標(biāo)記,true為打斷,false為未打斷

線程中調(diào)用wait方法

public class InterruptThread {

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

// 創(chuàng)建線程1
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "運(yùn)行......");
},"t1線程");

// 創(chuàng)建線程2
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "運(yùn)行......");
// 調(diào)用wait方法
try {
t1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2線程");
// 啟動線程
t1.start();
t2.start();
// t2線程插隊(duì)執(zhí)行
t2.join();
// 打斷t2線程
t2.interrupt();
// 查看t2線程打斷標(biāo)記
System.out.println("t2打斷標(biāo)記:" + t2.isInterrupted());
}
}

運(yùn)行結(jié)果:

  • 調(diào)用wait方法之后仍然會觸發(fā)異常
  • 打斷標(biāo)記被重置變?yōu)閒alse,所以可以在catch塊中設(shè)置打斷標(biāo)記為true

終止線程

此時可以根據(jù)打斷標(biāo)記,在線程內(nèi)部判斷是否需要打斷

案例:小明放假回家,媽媽想著給小明做好吃的,但是小明失戀了沒有胃口,就給媽媽說不要做了,媽媽收到打斷消息之后就停止做飯。

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

// 創(chuàng)建媽媽線程
Thread t1 = new Thread(() -> {
// 整東西吃
System.out.println("兒子放假了,整個燴面!吭哧吭哧~~~");
while (true) {
// 獲取當(dāng)前線程打斷狀態(tài)
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted) {
System.out.println("whis today,餓你輕!");
// 停止
break;
}
}
},"媽媽線程:");

// 創(chuàng)建小明線程
Thread t2 = new Thread(() -> {
System.out.println("媽,我不想吃,你別弄了......");
// 打斷媽媽線程
t1.interrupt();
},"小明線程:");

// 啟動線程
t1.start();
t2.start();
// 控制執(zhí)行順序
t1.join();
t2.join();
}
}

運(yùn)行結(jié)果:媽媽線程做飯,當(dāng)小明線程打斷之后,媽媽線程就收到打斷信息,停止運(yùn)行

通過代碼我們發(fā)現(xiàn),其實(shí)線程的終止權(quán)在被打斷的線程中,通過判斷打斷標(biāo)記來控制是否終止,也就是小明不讓媽媽做飯,媽媽可以不做也可以繼續(xù)做。

停止線程其他方法

  • 使用Thread類中的stop()方法
  1. 調(diào)用 stop() 方法會立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 語句中的,并拋出ThreadDeath異常(通常情況下此異常不需要顯示的捕獲),因此可能會導(dǎo)致一些清理性的工作的得不到完成,如關(guān)閉文件數(shù)據(jù)流,關(guān)閉數(shù)據(jù)庫連接等。
  2. 調(diào)用 stop() 方法會立即釋放該線程所持有的所有的鎖,導(dǎo)致數(shù)據(jù)得不到同步,出現(xiàn)數(shù)據(jù)不一致的問題。
  • 使用System.exit(int)方法
  1. 該方法會直接停止JVM,不單單停止一個線程,殺傷力太大

線程終止案例

小明是大強(qiáng)的秘書,負(fù)責(zé)記錄會議內(nèi)容,小明會每1秒記錄一次重要講話內(nèi)容,當(dāng)會議結(jié)束后,就終止記錄工作,但是在終止時會再整理一下會議內(nèi)容

會議線程:

public class MeetingThread {

// 開會
public void meeting() {
while (true) {
// 判斷是否結(jié)束會議
if(Thread.currentThread().isInterrupted()) {
System.out.println("會議結(jié)束,整理會議記錄");
break;
}

// 每1秒記錄一次重要內(nèi)容
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "記錄會議內(nèi)容......");
} catch (InterruptedException e) {
// 調(diào)用sleep,join,wait等方法,發(fā)生異常,打斷標(biāo)記會被重新設(shè)置為false,所以需要再次打斷
Thread.currentThread().interrupt();
}
}
}
}

測試類:

public class MeetingThreadMain {
public static void main(String[] args) throws InterruptedException {
MeetingThread meeting = new MeetingThread();

// 小明線程
Thread xiaoming = new Thread(() -> {
// 記錄會議內(nèi)容
meeting.meeting();
},"小明:");

xiaoming.start();

// 5秒后結(jié)束會議
Thread.sleep(5000);
// 打斷小明線程
xiaoming.interrupt();
System.out.println("會議結(jié)束......");
}
}

運(yùn)行結(jié)果:

如果沒有在catch中重新打斷線程,則會不斷記錄下去,記?。寒?dāng)線程中調(diào)用了sleep,wait,join方法時,線程的打斷標(biāo)記會被重置,需要在catch塊中重新打斷

isInterrupted 和 interrupted區(qū)別

文章開頭介紹過,兩個方法都是判斷線程的打斷狀態(tài),interrupted 是Thread類的靜態(tài)方法,獲取打斷狀態(tài)之后會重置打斷狀態(tài)為false,而isInterrupted是Thread對象的方法,非靜態(tài)方法不會重置打斷狀態(tài)

isInterrupted 方法

interrupted方法

發(fā)現(xiàn)查看小明線程狀態(tài)時已經(jīng)變?yōu)?false

注意:Thread.interrupted查看的是正在執(zhí)行的線程的狀態(tài),而isInterrupted是可以指定線程的,根據(jù)線程變量名查看狀態(tài)

LockSupport

LockSupport是JUC中一個工具類,構(gòu)造方法私有,并且不存在獲取實(shí)例的靜態(tài)方法,提供了一堆靜態(tài)方法幫助完成對線程的操作,主要是為了暫停和恢復(fù)線程,主要方法有兩個:

  • park():暫停線程
  • unpark(Thread thread):恢復(fù)指定線程對象運(yùn)行

park停止當(dāng)前線程

import java.util.concurrent.locks.LockSupport;

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

Thread t1 = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println(current.getName() + "啟動,被park");
// 停止當(dāng)前線程
LockSupport.park();
System.out.println(current.getName() + "park之后");
// 輸出線程狀態(tài)
System.out.println(current.getName() + "打斷狀態(tài)" + current.isInterrupted());
}, "t1");
t1.start();
}
}

運(yùn)行結(jié)果:發(fā)現(xiàn)線程執(zhí)行啟動后就停止,因?yàn)橛龅搅薒ockSupport.park();導(dǎo)致t1線程停止運(yùn)行,但是線程還未結(jié)束,所以程序并未停止【左側(cè)紅色小框標(biāo)識】

打斷park線程:如下圖,在main線程中,等待 1秒 之后,執(zhí)行t1.interrupt();,打斷t1線程,此時t1線程已經(jīng)被停止運(yùn)行,發(fā)現(xiàn)調(diào)用中斷方法之后,t1線程又繼續(xù)執(zhí)行,并且打斷標(biāo)記為true

park細(xì)節(jié)

如果park之后,阻塞線程,繼續(xù)執(zhí)行,再次調(diào)用LockSupport.park();方法阻塞線程時無效,如下進(jìn)行阻塞二次:

import java.util.concurrent.locks.LockSupport;

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

Thread t1 = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println(current.getName() + "啟動,被park");
// 停止當(dāng)前線程
LockSupport.park();
System.out.println(current.getName() + "park之后");
// 輸出線程狀態(tài)
System.out.println(current.getName() + "打斷狀態(tài)" + current.isInterrupted());
// 再次打斷線程
LockSupport.park();
System.out.println(current.getName() + "再次打斷無效");
}, "t1");
t1.start();
// 停止1S后,打斷t1線程
Thread.sleep(1000);
t1.interrupt();
}
}

運(yùn)行結(jié)果:

如果你還想再次打斷,可以調(diào)用一次 Thread.interrupted() 獲取線程打斷狀態(tài),并且再設(shè)置為false,發(fā)現(xiàn)已成功阻塞

unpark:恢復(fù)指定線程對象

  • t1線程執(zhí)行2S后被阻塞
  • 主線程中等待1S后喚醒t1線程

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.locks.LockSupport;

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

Thread t1 = new Thread(() -> {
Thread current = Thread.currentThread();
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "-" + current.getName() + ":啟動");
try {
// 睡眠1秒后停止線程
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "-" + current.getName() + ":park");
// 停止當(dāng)前線程
LockSupport.park();
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "-" + current.getName() + ":繼續(xù)執(zhí)行");
}, "t1");
// 啟動t1線程
t1.start();
// 停止2S
Thread.sleep(2000);
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "-喚醒t1線程");
// 喚醒t1線程
LockSupport.unpark(t1);
}
}

執(zhí)行后發(fā)現(xiàn):t1線程被正常喚醒,繼續(xù)執(zhí)行

如果我們調(diào)換一下阻塞和喚醒的順序,是否仍然可以正常喚醒呢?也就是:先喚醒t1線程,t1線程再進(jìn)入阻塞狀態(tài),發(fā)現(xiàn)仍然可以喚醒成功

為什么可以先調(diào)用unpark在調(diào)用park呢?

這是一道高頻面試題,在下邊也有總結(jié),在這里我們不妨先分析一下,這涉及到了 park和unpark原理

當(dāng)我們調(diào)用park方法時其實(shí)調(diào)用的是 sun.misc.Unsafe UNSAFE 類的 park 方法,這里提到【許可證】這個詞,就是park和unpark的關(guān)鍵

每個線程都有一個自己的Parker對象,該對象由三部分組成_counter、_cond、_mutex

可以想一下,unpark其實(shí)就相當(dāng)于一個許可,告訴特定工廠你可以繼續(xù)生產(chǎn),特定工廠想要park停止生產(chǎn)的時候一看到有許可,就可以會繼續(xù)運(yùn)行。因此其執(zhí)行順序可以顛倒。

Parker實(shí)例:

park方法:

當(dāng)調(diào)用park()時,先嘗試能否直接拿到【許可】,即_counter>0,如果獲取成功,則把_counter設(shè)置為0,并返回

如果不成功,則構(gòu)造一個ThreadBlockInVM,然后檢查_counter是不是>0,如果是,則把_counter設(shè)置為0,并返回

否則,再判斷等待的時間,然后再調(diào)用pthread_cond_wait函數(shù)等待,如果等待返回,則把_counter設(shè)置為0并返回:

這就是整個park的過程,總結(jié)來說就是消耗【許可】的過程。

unpark方法

JDK源碼unpark方法其實(shí)也是調(diào)用了sun.misc.Unsafe UNSAFE類的unpark方法,注釋的意思是給線程【許可證】

當(dāng)調(diào)用unpark()時,直接設(shè)置_counter為1,如果_counter之前的值是0,則還要調(diào)用pthread_cond_signal喚醒在park中等待的線程:

_counter的值最大為1,即使多次調(diào)用unpark()許可證的個數(shù)也最多是1

即使我們先調(diào)用了unpark,再調(diào)用park也是可以正常喚醒線程的,因?yàn)閡npark獲取了一個許可證,之后再調(diào)用park方法,就可以名正言順的憑證消費(fèi),故不會阻塞。

思考:如果喚醒兩次后阻塞兩次,會是什么結(jié)果呢?

許可的數(shù)量最多為1,連續(xù)調(diào)用兩次unpark和調(diào)用一次unpark效果一樣,只會增加一個許可;而調(diào)用兩次park卻需要消費(fèi)兩個許可,證不夠,不能放行。線程就會阻塞

總結(jié)

  • 調(diào)用park()時判斷許可是否大于0,如果大于0繼續(xù)運(yùn)行,如果不大于0線程就進(jìn)入阻塞,此時會再創(chuàng)建一個 ThreadBlockInVM 許可是否大于0,如果大于0就將許可設(shè)置為0,放行運(yùn)行
  • 調(diào)用unpark()將許可設(shè)置為1,無論調(diào)用多少次都是1,如果許可為0,則還會調(diào)用 pthread_cond_signal喚醒在park中的線程
  • 先調(diào)用unpark,再調(diào)用park,線程仍然可以被喚醒繼續(xù)執(zhí)行

根據(jù)合集中《Java線程通信》一文,我們將線程阻塞和喚醒的4種方案介紹完畢,如果在工作或者面試中碰到一定要想起來使用方法和細(xì)節(jié)

文章出自:石添的編程哲學(xué),如有轉(zhuǎn)載本文請聯(lián)系【石添的編程哲學(xué)】今日頭條號。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2023-05-12 14:14:00

Java線程中斷

2025-02-05 14:28:19

2021-06-04 10:52:51

kubernetes場景容器

2023-12-20 10:04:45

線程池Java

2024-04-23 09:35:27

線程終止C#多線程編程

2023-12-21 10:26:30

??Prettier

2022-05-13 08:48:50

React組件TypeScrip

2021-06-25 15:53:25

Kubernetes程序技巧

2012-02-29 13:39:18

AndroidGoogle

2022-03-11 12:14:43

CSS代碼前端

2025-04-21 00:00:05

2024-11-25 13:49:00

2021-12-29 17:24:16

Kubernetes集群事件

2021-02-23 08:02:23

線程volatileinterrupt

2022-06-28 08:01:26

hook狀態(tài)管理state

2018-07-12 14:20:33

SQLSQL查詢編寫

2024-05-24 10:51:51

框架Java

2022-03-08 06:41:35

css代碼

2020-04-03 14:55:39

Python 代碼編程

2024-02-23 08:57:42

Python設(shè)計模式編程語言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號