Java執行緒的常用知識
之前梳理了程序和執行緒的概念以及如何建立、結束程序和執行緒,本文總結一下執行緒使用過程中的知識點:
生命週期及狀態變遷
-
執行緒的生命週期包括:新建、就緒、執行、阻塞、死亡這幾個狀態。
-
當執行緒被new之後,處於新建狀態,並不是立馬就被執行了,此時執行緒被虛擬機器分配了記憶體以及初始化了執行緒內的變數。
-
呼叫thread物件的start方法時,執行緒處於就緒狀態,就緒的執行緒並不一定立馬就被執行,僅僅只是建立了方法棧和計數器,需要等待JVM根據優先順序、演算法等規則排程之後才會進入執行狀態。
-
執行緒執行之後,因為前文說過的,多個執行緒之間通過輪流切換的方式使用CPU資源,所以JVM將CPU資源排程給別的執行緒後,當前執行緒就從執行狀態轉為就緒狀態。
-
當執行緒呼叫sleep方法、或者執行一個阻塞時的IO方法且沒有返回結果時、呼叫了suspend(掛起)方法、呼叫一個同步物件但當前物件正在被其他執行緒鎖持有時,執行緒就會進入阻塞狀態。
-
上文說到的幾種方法,可以使執行緒進入死亡狀態,該執行緒就結束了。
狀態過程相關注意事項
- 執行緒只能呼叫start方法,不能呼叫run方法,呼叫run方法只是將thread物件當作一個普通的物件來執行。
- 除了新建狀態外,其他狀態不能再呼叫start方法,否則會丟擲IllegalThreadStateException異常。
- Thread.sleep()方法會使當前正在被執行的執行緒睡眠,讓CPU可以去啟動另一個處於就緒的執行緒,但是該方法在睡眠期間被喚醒會丟擲InterruptedException異常。(上文停止執行緒也用到該原理)
- 當時sleep時間結束後、呼叫的IO方法等待返回結果時、獲取同步鎖物件時、掛起的執行緒呼叫了resume方法時該執行緒就出於就緒狀態,等待CPU排程。
- 主執行緒執行完之後,不影響其他執行緒的執行,每一個執行緒執行起來後,級別相同。
- 執行緒分守護執行緒 和普通執行緒 ,守護執行緒時JVM自己使用的執行緒,如垃圾回收執行緒,使用者建立的執行緒基本上為普通執行緒,也可以自動設定Thread物件的setDaemon(true)將執行緒改為守護執行緒(在start之前呼叫)。Thread物件的isDaemon方法可以查詢是否時守護執行緒。
- 使用者建立的執行緒設定為守護執行緒後,不管是否執行完,當程序中所有普通執行緒執行完,整個程序也就結束。
public class ThreadTest { public static void main(String[] args) { FutureTask futureTask = new FutureTask(new Callable<Integer>() { @Override public Integer call() throws Exception { sleep(50000); System.out.println("FutureTask 等待50秒後返回100"); return 100; } }); Thread thread = new Thread(futureTask, "futureTask"); thread.setDaemon(true); thread.start(); System.out.println("主執行緒執行完畢"); } } //執行結果: 主執行緒執行完畢 Process finished with exit code 0
- Thread物件的setPriority(int newPriority)可以設定執行緒優先順序,範圍從0~10,值越大,級別越高,級別越高,執行的機會就越多,不設定的話,預設級別和其父程序級別相同。
執行緒sleep和yield方法區別
- sleep方法會讓執行緒進入阻塞狀態,在其睡眠時間內,該執行緒不會被執行。
- yield方法只是將執行緒處於就緒狀態,可能排程器又立馬排程了該執行緒。
- yield方法暫停後,和該執行緒優先順序相同或者高的執行緒優先執行。
- sleep之後,其他執行緒執行的機會有JVM決定,不一定就是高優先順序的。
- yield不會丟擲異常,sleep會丟擲InterruptedException異常。
public class ThreadTest { public static void main(String[] args) { SellRunnable sellRunnable = new SellRunnable(); Thread thread1 = new Thread(sellRunnable, "1"); thread1.setPriority(1); Thread thread2 = new Thread(sellRunnable, "2"); thread2.setPriority(5); Thread thread3 = new Thread(sellRunnable, "3"); thread3.setPriority(5); thread2.start(); thread1.start(); thread3.start(); } } class SellRunnable implements Runnable { //有十張票 int index = 10; public synchronized void sell() { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if(index>=1){ index--; System.out.println("售貨視窗:" + Thread.currentThread().getName() + "賣出了一張票,剩餘:" + index); }else{ System.out.println("售貨視窗:" + Thread.currentThread().getName() + " 買票時沒票了"); } } @Override public void run() { while (index > 0) { System.out.println("售貨視窗:" + Thread.currentThread().getName() + " 開始買票"); sell(); } } } //執行結果: 售貨視窗:2 開始買票 售貨視窗:3 開始買票 售貨視窗:1 開始買票 售貨視窗:2賣出了一張票,剩餘:9 售貨視窗:2 開始買票 售貨視窗:1賣出了一張票,剩餘:8 售貨視窗:1 開始買票 售貨視窗:3賣出了一張票,剩餘:7 售貨視窗:3 開始買票 售貨視窗:3賣出了一張票,剩餘:6 售貨視窗:3 開始買票 售貨視窗:1賣出了一張票,剩餘:5 售貨視窗:1 開始買票 售貨視窗:2賣出了一張票,剩餘:4 售貨視窗:2 開始買票 售貨視窗:2賣出了一張票,剩餘:3 售貨視窗:2 開始買票 售貨視窗:2賣出了一張票,剩餘:2 售貨視窗:2 開始買票 售貨視窗:2賣出了一張票,剩餘:1 售貨視窗:2 開始買票 售貨視窗:2賣出了一張票,剩餘:0 售貨視窗:1 買票時沒票了 售貨視窗:3 買票時沒票了 Process finished with exit code 0//可以看出,2、3執行的相對較多 //加入sleep @Override public void run() { while (index > 0) { try { Thread.sleep(100); System.out.println("售貨視窗:" + Thread.currentThread().getName() + " 開始買票"); sell(); } catch (InterruptedException e) { e.printStackTrace(); } } } //執行結果: 售貨視窗:1 開始買票 售貨視窗:2 開始買票 售貨視窗:3 開始買票 售貨視窗:1賣出了一張票,剩餘:9 售貨視窗:3賣出了一張票,剩餘:8 售貨視窗:2賣出了一張票,剩餘:7 售貨視窗:3 開始買票 售貨視窗:2 開始買票 售貨視窗:1 開始買票 售貨視窗:3賣出了一張票,剩餘:6 售貨視窗:1賣出了一張票,剩餘:5 售貨視窗:2賣出了一張票,剩餘:4 售貨視窗:3 開始買票 售貨視窗:2 開始買票 售貨視窗:1 開始買票 售貨視窗:3賣出了一張票,剩餘:3 售貨視窗:1賣出了一張票,剩餘:2 售貨視窗:2賣出了一張票,剩餘:1 售貨視窗:3 開始買票 售貨視窗:2 開始買票 售貨視窗:1 開始買票 售貨視窗:3賣出了一張票,剩餘:0 售貨視窗:1 買票時沒票了 售貨視窗:2 買票時沒票了 Process finished with exit code 0// sleep之後,各個視窗差不多 //加入yield @Override public void run() { while (index > 0) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("售貨視窗:" + Thread.currentThread().getName() + " 開始買票"); sell(); if ("2".equals(Thread.currentThread().getName())) { Thread.yield(); } } } //執行結果: 售貨視窗:2 開始買票 售貨視窗:3 開始買票 售貨視窗:1 開始買票 售貨視窗:2賣出了一張票,剩餘:9 售貨視窗:2 開始買票 售貨視窗:1賣出了一張票,剩餘:8 售貨視窗:1 開始買票 售貨視窗:3賣出了一張票,剩餘:7 售貨視窗:1賣出了一張票,剩餘:6 售貨視窗:3 開始買票 售貨視窗:1 開始買票 售貨視窗:3賣出了一張票,剩餘:5 售貨視窗:3 開始買票 售貨視窗:2賣出了一張票,剩餘:4 售貨視窗:2 開始買票 售貨視窗:3賣出了一張票,剩餘:3 售貨視窗:3 開始買票 售貨視窗:1賣出了一張票,剩餘:2 售貨視窗:1 開始買票 售貨視窗:3賣出了一張票,剩餘:1 售貨視窗:3 開始買票 售貨視窗:2賣出了一張票,剩餘:0 售貨視窗:3 買票時沒票了 售貨視窗:1 買票時沒票了 Process finished with exit code 0 // 2變少,相對3變多(執行太快,yield看不出效果)