Java併發程式設計之執行緒生命週期、守護執行緒、優先順序和join、sleep、yield
Java併發程式設計中,其中一個難點是對執行緒生命週期的理解,和多種執行緒控制方法、執行緒溝通方法的靈活運用。這些方法和概念之間彼此聯絡緊密,共同構成了Java併發程式設計基石之一。
Java執行緒的生命週期
Java執行緒類定義了New、Runnable、Running Man、Blocked和Dead五種狀態。
New
當初始化了一個執行緒物件之後,執行緒就進入了New的狀態。此時JVM會為其分配堆記憶體、初始化成員變數的值,跟一般的物件一樣。
Runnable
當呼叫執行緒物件的start方法之後,就進入Runnable狀態。JVM會為其建立虛擬機器棧和程式計數器。此時僅表面執行緒可以開始執行,但何時執行取決於JVM的執行緒排程器。
Running
當執行緒獲取到CPU資源的時候,就進入Running狀態執行執行緒方法了。如果執行緒數大於多處理器的數目,會存在多個執行緒輪換,儘管多個處理器會同時並行處理幾個執行緒。
執行緒排程的細節取決於底層平臺,當Running的執行緒呼叫其yield方法或失去CPU資源的時候,即回到Runnable狀態。
Blocked
當發生如下情況,執行緒會被阻塞/重新進入Runnable狀態:
1. 執行緒呼叫sleep方法 ===> sleep方法經過指定時間
2. 執行緒呼叫了一個阻塞式IO ===> 呼叫的阻塞式IO方法返回
3. 試圖獲取一個正被使用的同步鎖 ===> 成功獲取同步鎖
4. 等待notify ===> 其它執行緒發出了notify
5. 執行緒呼叫suspend方法(容易導致死鎖,不建議使用) ===> 被呼叫了resume方法
Dead
當發生如下情況,執行緒結束
1. 執行緒執行體完成
2. 丟擲未捕獲的異常或錯誤
3. 直接呼叫stop方法(容易導致死鎖,不建議使用)
可通過呼叫執行緒物件的isAlive方法,如果處於新建和死亡狀態會返回false
執行緒管理
常用的執行緒管理包括設定後臺執行緒、設定優先順序、join、sleep和yield
設定後臺執行緒
setDaemon(true):設定為後臺執行緒
isDaemon():用於判斷指定執行緒是否為後臺執行緒
設定優先順序
setPriority(int priority):設定優先順序
getPriority():獲取優先順序
1 public class ThreadPriority { 2 3 public static void main(String[] args) { 4 Thread t1 = new Thread(()-> 5 { 6 while(true) { 7 System.out.println("t11111"); 8 } 9 }, "t1"); 10 t1.setPriority(Thread.NORM_PRIORITY); 11 12 Thread t2 = new Thread(()->{ 13 while (true) { 14 System.out.println("t22222"); 15 } 16 }); 17 t2.setPriority(10); 18 19 t1.start(); 20 t2.start(); 21 } 22 23 }View Code
join
join執行緒可以理解為把一個問題分為若干小問題由不同的執行緒處理,其它執行緒處理過程中,呼叫join方法的執行緒處於阻塞狀態,在其它執行緒處理完畢後,再回到執行狀態的概念。
1 public class ThreadJoin { 2 3 private static void shortSleep() { 4 try { 5 TimeUnit.SECONDS.sleep(1); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 } 10 11 private static Thread create(int seq) { 12 return new Thread(() -> { 13 for (int i = 0; i < 10; i++) { 14 System.out.println(Thread.currentThread().getName() + "#" + i); 15 shortSleep(); 16 } 17 }, String.valueOf(seq)); 18 } 19 20 public static void main(String[] args) throws InterruptedException { 21 22 List<Thread> threads = IntStream.range(1, 3).mapToObj(ThreadJoin::create).collect(Collectors.toList()); 23 24 threads.forEach(Thread::start); 25 //main執行緒呼叫join方法, 會進入阻塞, 等其它執行緒完成了再行繼續 26 for(Thread thread : threads) { 27 thread.join(); 28 } 29 30 for(int i = 0; i < 10; i++) { 31 System.out.println(Thread.currentThread().getName() + "#" + i); 32 shortSleep(); 33 } 34 } 35 }View Code
sleep
Thread類的靜態方法,用於暫停執行緒的執行。一般建議使用1.5後新增的TimeUnit類來更好的暫停執行緒
1 public class ThreadSleep { 2 3 private static void sleep(int ms) { 4 try { 5 TimeUnit.SECONDS.sleep(ms); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 } 10 11 public static void main(String[] args) { 12 13 new Thread(() -> 14 { 15 long startTime = System.currentTimeMillis(); 16 sleep(2); 17 long endTime = System.currentTimeMillis(); 18 System.out.println(String.format("Total spend %d second", (endTime - startTime))); 19 }).start(); 20 21 /* 22 * Thread sleep times depends on your system 23 */ 24 long startTime = System.currentTimeMillis(); 25 sleep(3); 26 long endTime = System.currentTimeMillis(); 27 System.out.println(String.format("Main thread total spend %d second", (endTime - startTime))); 28 29 } 30 }View Code
yield
與sleep方法不同,yield方法只是讓當前執行緒暫停一下,以便執行緒排程器操作執行緒排程。該方法不會讓執行緒進入阻塞
1 public class ThreadYield { 2 3 private static Thread create(int index) { 4 try { 5 TimeUnit.SECONDS.sleep(1); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 return new Thread(()-> 10 { 11 if (index == 0) { 12 Thread.yield(); 13 } 14 System.out.println(index); 15 }); 16 } 17 18 public static void main(String[] args) { 19 IntStream.range(0, 2).mapToObj(ThreadYield::create).forEach(Thread::start); 20 } 21 }View Code