併發程式設計系列學習筆記02(Java中的執行緒)
Java執行緒
建立和執行執行緒
- Thread
public static void main(String[] args) {
// 建立執行緒物件 lambda
Thread thread = new Thread(() -> log.info("子執行緒執行"));
// 設定執行緒名稱
thread.setName("thread-0000");
// 啟動執行緒
thread.start();
log.info("主執行緒執行完畢");
}
- Runnable 配合 Thread
public static void main(String[] args) {
Runnable task = new Runnable() {
@Override
public void run() {
log.info("子執行緒已執行");
}
};
Thread thread = new Thread(task, "thread-0001");
thread.start();
log.info("主執行緒執行完畢");
}
-
使用Runnable小結
- 將執行緒與任務解耦
- 從而更容易與執行緒池等高階API配合使用
- 脫離Thread繼承體系,更靈活
-
FutureTask 配合 Thread
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<>(() -> {
log.info("FutureTask執行");
Thread.sleep(1000);
return 100;
});
Thread thread = new Thread(futureTask, "thread-0002");
thread.start();
// 獲得返回值,阻塞執行緒未執行完時,將阻塞等待返回結果
Integer result = futureTask.get();
log.info("獲得返回值 {} ", result);
log.info("主執行緒執行完畢");
}
觀察多個執行緒執行情況
- 交替執行
- 執行的先後順序不由我們控制
檢視程序與執行緒
-
windows
- 檢視程序:tasklist
- 殺死程序:taskkill
-
linux
- 檢視所有程序 ps -ef
- 檢視某程序下所有執行緒 ps -fT -p <PID>
- 殺死程序 kill
- 顯示程序 top 按 H 切換執行緒
- top -H -p <PID> 檢視某個PID下所有執行緒
-
Java相關
- 檢視所有Java 程序 jps
- 檢視某個Java程序所有執行緒 狀態 jstack <PID>
- 圖形介面,支援遠端 jconsole
執行緒執行的原理
-
棧與棧楨
- JVM主要由堆、棧、方法區組成
- 堆記憶體其實就是分給一個個執行緒使用的
- 每個執行緒啟動後,虛擬機器會為其分配一塊棧記憶體
- 每個棧由多個棧楨(Frame)組成,棧楨對應每次方法呼叫所佔記憶體
- 一個執行緒同時只能有一個活動棧楨,對應正在執行的那個method
-
執行緒上下文切換
-
由於CPU會在不同執行緒間執行切換,切換時,作業系統將儲存當前執行緒狀態,並恢復另一個執行緒狀態
-
對應Java中的程式計數器概念,記錄下一條JVM指令的執行地址,為執行緒私有
-
執行緒狀態資訊包括
- 程式計數器
- 每個棧楨的資訊
- 如:區域性變數、運算元棧、返回地址等
-
頻繁發生執行緒上下文切換影響效能
-
常見方法
- start() // 啟動新執行緒,讓執行緒進入就緒狀態
- run() //執行緒被執行後執行的方法
- join() // 等待執行緒執行結束
- join(long n) // 等待結束帶超時時間
- getId() // 執行緒ID
- getName() // 執行緒名稱
- setName(String name) //修改名稱
- getPriority() // 獲取執行緒執行的優先順序
- setPriority(int i) // 設定優先順序,1-10,值越大被CPU排程的機率越高
- getState() // 獲取狀態,一共6個
- isInterrupted() // 判斷是否被打斷,不會清除打斷標記
- isAlive() // 判斷執行緒是否存活,還沒執行完畢
- interrupt() // 打斷執行緒,將丟擲異常,並清除打斷標記(執行緒未在執行)
- interrupted() // 判斷是否被打斷,同時會清除打斷標記
- currentThread() // 獲取當前正在執行的執行緒
- sleep(long n) // 讓執行緒休眠 n 毫秒,讓出CPU時間片
- yield() // 提示執行緒排程器讓出當前執行緒的CPU,一般用於測試或除錯
start 與 run
- 直接呼叫run,則會在主執行緒中直接執行run,不會啟動新的執行緒
- 呼叫 start 才會啟動新執行緒,並通過執行緒間接執行run方法
sleep 與 yield
-
sleep會讓執行緒狀態從Running 進入 Timed Waiting,讓執行緒睡眠,染出CPU的時間片給其他執行緒
-
yield 是 讓執行緒 從Running進入Runnable,主動讓出CPU對執行緒排程,一般用於除錯
-
執行緒優先順序
- 僅用於提示排程器優先排程該執行緒,但排程器可能忽略
- 僅在CPU比較繁忙時才有一定效果,CPU較空閒時幾乎沒有作用
join
- 用於等待指定執行緒執行完畢
- 可設定超時時間,超過時間後,將不再等待
interrupt
- 打斷執行緒執行,並丟擲異常
- 打斷呼叫了sleep的執行緒,被清空其打斷標記,打斷狀態為false
- 打斷正在執行的執行緒,不會清空,打斷狀態為true
- 如果執行緒打斷標記是true,此時呼叫LockSupport.park()暫停執行緒會失效
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
Sleeper.sleep(1);
}, "t1");
t1.start();
Sleeper.sleep(0.5);
// 打斷t1執行緒
t1.interrupt();
log.info("打斷執行緒t1後,t1的狀態為 {}", t1.isInterrupted());
}
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at cn.com.ebidding.test.Sleeper.sleep(Sleeper.java:14)
at cn.com.ebidding.test.TestInterrupt.lambda$main$0(TestInterrupt.java:15)
at java.lang.Thread.run(Thread.java:748)
14:37:19.495 [main] INFO cn.com.ebidding.test.TestInterrupt - 打斷執行緒t1後,t1的狀態為 false
幾個不推薦方法
- stop(停止)、suspend(掛起,暫停)、resume(恢復)
- 已過時,容易破壞同步程式碼塊,最後造成死鎖
主執行緒與守護執行緒
- Java程序預設需等待所有執行緒執行結束,程序才會結束
- 守護執行緒即其他非守護執行緒已結束後,自己會強制結束
- 設定:t1.setDaemon(true)
- 垃圾回收執行緒就是守護執行緒
- Tomcat中的Acceptor 與 Poller 也是,故當Tomcat 執行 shutdown後,不會等待他們處理完當前正在處理的請求
作業系統中的執行緒狀態(五種)
- 初始:剛建立,還未與作業系統關聯
- 可執行:與作業系統已關聯,可由CPU排程
- 執行:已獲取到了CPU時間片,正在執行中;CPU時間片用完,執行中將轉換為可執行,其會導致執行緒上下文切換
- 阻塞:此時執行緒不會用到CPU,但會導致執行緒上下文切換,例如BIO讀寫檔案場景;注意阻塞與可執行加以區分,阻塞狀態的執行緒只要不喚醒,排程器一直不會考慮排程;
- 終止:已執行完畢生命週期接結束,不會再轉換為其他狀態
Java JVM中的執行緒狀態(六種)
- New:剛建立,還未呼叫start
- Runnable:覆蓋了作業系統中的可執行、執行、阻塞(IO讀寫)
- Blocked:阻塞狀態的細分
- Waiting:阻塞狀態的細分
- Timed_waiting:阻塞狀態的細分
- Terminated:執行緒程式碼執行結束
「其他文章」
- 關於 Druid 與 MySQL-Connector8.x 引發的 boot.loader 包物件無法回收的坑
- VMware 虛擬機器安裝CentOS7:記錄幾個常見問題
- 設計模式:Mybatis中應用的經典設計模式
- 併發程式設計系列學習筆記08(AQS & JUC)
- 使用IDEA提供的HTTP Client替代Postman
- 併發程式設計系列學習筆記07(執行緒池)
- 併發程式設計系列學習筆記06(共享模型之不可變)
- 微服務:鏈路追蹤元件skywalking入門
- SpringCloud註冊中心由Eureka改為Zookeeper
- 併發程式設計系列學習筆記05(共享模型之無鎖併發)
- 併發程式設計系列學習筆記04(共享模型之記憶體)
- 併發程式設計系列學習筆記03(共享模型之管程)
- 併發程式設計系列學習筆記02(Java中的執行緒)
- 併發程式設計系列學習筆記01(程序與執行緒)
- windows如何修改複製貼上與剪下快捷鍵?
- 記一次RocketMQ Cloud Alibaba版本依賴衝突問題
- idea如何離線構建Maven專案
- 多執行緒系列【作業系統底層工作原理】(一)
- MySQL架構課程學習筆記(二)
- MySQL架構課程學習筆記(一)