成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

領導說誰再用Stop直接下崗,這樣終止線程更優雅

開發 前端
即使我們先調用了unpark,再調用park也是可以正常喚醒線程的,因為unpark獲取了一個許可證,之后再調用park方法,就可以名正言順的憑證消費,故不會阻塞。

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

  • 通過Thread類提供的方法中斷線程
  • 中斷線程的應用場景和代碼實現,以及實現中的細節處理
  • stop方法中斷線程存在的隱患
  • LockSupport停止和喚醒線程
  • LockSupport工具類的park和unpark的原理

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

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

打斷線程場景

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

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

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

Thread類相關API

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

另外需要注意的是:interrupted()方法是獲取當前調用線程【正在運行的線程】的中斷標志,而不是調用interrupted()方法的線程的中斷標志。

打斷線程

public class InterruptThread {

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

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

運行結果:

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

線程中調用wait方法

public class InterruptThread {

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

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

// 創建線程2
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "運行......");
// 調用wait方法
try {
t1.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2線程");
// 啟動線程
t1.start();
t2.start();
// t2線程插隊執行
t2.join();
// 打斷t2線程
t2.interrupt();
// 查看t2線程打斷標記
System.out.println("t2打斷標記:" + t2.isInterrupted());
}
}

運行結果:

  • 調用wait方法之后仍然會觸發異常
  • 打斷標記被重置變為false,所以可以在catch塊中設置打斷標記為true

終止線程

此時可以根據打斷標記,在線程內部判斷是否需要打斷

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

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

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

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

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

運行結果:媽媽線程做飯,當小明線程打斷之后,媽媽線程就收到打斷信息,停止運行

通過代碼我們發現,其實線程的終止權在被打斷的線程中,通過判斷打斷標記來控制是否終止,也就是小明不讓媽媽做飯,媽媽可以不做也可以繼續做。

停止線程其他方法

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

線程終止案例

小明是大強的秘書,負責記錄會議內容,小明會每1秒記錄一次重要講話內容,當會議結束后,就終止記錄工作,但是在終止時會再整理一下會議內容

會議線程:

public class MeetingThread {

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

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

測試類:

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

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

xiaoming.start();

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

運行結果:

如果沒有在catch中重新打斷線程,則會不斷記錄下去,記?。寒斁€程中調用了sleep,wait,join方法時,線程的打斷標記會被重置,需要在catch塊中重新打斷

isInterrupted 和 interrupted區別

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

isInterrupted 方法

interrupted方法

發現查看小明線程狀態時已經變為 false

注意:Thread.interrupted查看的是正在執行的線程的狀態,而isInterrupted是可以指定線程的,根據線程變量名查看狀態

LockSupport

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

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

park停止當前線程

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");
// 停止當前線程
LockSupport.park();
System.out.println(current.getName() + "park之后");
// 輸出線程狀態
System.out.println(current.getName() + "打斷狀態" + current.isInterrupted());
}, "t1");
t1.start();
}
}

運行結果:發現線程執行啟動后就停止,因為遇到了LockSupport.park();導致t1線程停止運行,但是線程還未結束,所以程序并未停止【左側紅色小框標識】

打斷park線程:如下圖,在main線程中,等待 1秒 之后,執行t1.interrupt();,打斷t1線程,此時t1線程已經被停止運行,發現調用中斷方法之后,t1線程又繼續執行,并且打斷標記為true

park細節

如果park之后,阻塞線程,繼續執行,再次調用LockSupport.park();方法阻塞線程時無效,如下進行阻塞二次:

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");
// 停止當前線程
LockSupport.park();
System.out.println(current.getName() + "park之后");
// 輸出線程狀態
System.out.println(current.getName() + "打斷狀態" + current.isInterrupted());
// 再次打斷線程
LockSupport.park();
System.out.println(current.getName() + "再次打斷無效");
}, "t1");
t1.start();
// 停止1S后,打斷t1線程
Thread.sleep(1000);
t1.interrupt();
}
}

運行結果:

如果你還想再次打斷,可以調用一次 Thread.interrupted() 獲取線程打斷狀態,并且再設置為false,發現已成功阻塞

unpark:恢復指定線程對象

  • t1線程執行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");
// 停止當前線程
LockSupport.park();
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "-" + current.getName() + ":繼續執行");
}, "t1");
// 啟動t1線程
t1.start();
// 停止2S
Thread.sleep(2000);
System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")) + "-喚醒t1線程");
// 喚醒t1線程
LockSupport.unpark(t1);
}
}

執行后發現:t1線程被正常喚醒,繼續執行

如果我們調換一下阻塞和喚醒的順序,是否仍然可以正常喚醒呢?也就是:先喚醒t1線程,t1線程再進入阻塞狀態,發現仍然可以喚醒成功

為什么可以先調用unpark在調用park呢?

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

當我們調用park方法時其實調用的是 sun.misc.Unsafe UNSAFE 類的 park 方法,這里提到【許可證】這個詞,就是park和unpark的關鍵

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

可以想一下,unpark其實就相當于一個許可,告訴特定工廠你可以繼續生產,特定工廠想要park停止生產的時候一看到有許可,就可以會繼續運行。因此其執行順序可以顛倒。

Parker實例:

park方法:

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

如果不成功,則構造一個ThreadBlockInVM,然后檢查_counter是不是>0,如果是,則把_counter設置為0,并返回

否則,再判斷等待的時間,然后再調用pthread_cond_wait函數等待,如果等待返回,則把_counter設置為0并返回:

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

unpark方法

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

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

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

即使我們先調用了unpark,再調用park也是可以正常喚醒線程的,因為unpark獲取了一個許可證,之后再調用park方法,就可以名正言順的憑證消費,故不會阻塞。

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

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

總結

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

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

文章出自:石添的編程哲學,如有轉載本文請聯系【石添的編程哲學】今日頭條號。

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2023-05-12 14:14:00

Java線程中斷

2021-06-04 10:52:51

kubernetes場景容器

2025-02-05 14:28:19

2023-12-21 10:26:30

??Prettier

2023-12-20 10:04:45

線程池Java

2022-05-13 08:48:50

React組件TypeScrip

2024-04-23 09:35:27

線程終止C#多線程編程

2024-11-25 13:49:00

2021-06-25 15:53:25

Kubernetes程序技巧

2022-03-11 12:14:43

CSS代碼前端

2012-02-29 13:39:18

AndroidGoogle

2025-04-21 00:00:05

2021-12-29 17:24:16

Kubernetes集群事件

2018-07-12 14:20:33

SQLSQL查詢編寫

2022-06-28 08:01:26

hook狀態管理state

2024-05-24 10:51:51

框架Java

2021-02-23 08:02:23

線程volatileinterrupt

2020-04-03 14:55:39

Python 代碼編程

2024-02-23 08:57:42

Python設計模式編程語言

2022-03-08 06:41:35

css代碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品久久久久久一区二区 | 亚洲天堂成人在线视频 | 天堂在线一区 | 丁香五月缴情综合网 | 国产精品久久久久久久久久久久久久 | 精品综合久久久 | 精品自拍视频在线观看 | 人人射人人草 | 日韩av成人在线观看 | 国产成人在线观看免费 | 四虎精品在线 | 亚洲精品久久久蜜桃网站 | 999久久久 | 在线观看中文字幕视频 | 日韩精品视频在线 | 午夜精品网站 | 欧美日韩久久久久 | 日韩一区二区福利视频 | 国产福利91精品 | 中文字幕第一页在线 | 国产精品永久 | 亚洲美乳中文字幕 | 久久精品欧美一区二区三区麻豆 | 中文在线播放 | 国产精品99久久久精品免费观看 | 成人av免费| 欧美精品久久久久久 | 国产精品a级 | 欧美激情国产日韩精品一区18 | 午夜男人视频 | 国产99久久精品 | 成人国产午夜在线观看 | 亚洲天堂久久新 | 日韩欧美一区二区在线播放 | 国产一区高清 | 国产探花在线精品一区二区 | 国产欧美一级二级三级在线视频 | 日本成人在线免费视频 | 日韩一区二区不卡 | 久久久日韩精品一区二区三区 | 亚洲国产精品一区二区三区 |