JVM中的執行緒行為
JVM做了它想做的事情,那麼如何預測執行緒執行的順序呢?
執行緒化是指同時執行程式過程以提高應用程式效能的實踐。雖然直接在業務應用程式中使用執行緒並不常見,但它們一直在Java框架中使用。例如,處理大量資訊的框架(如 Spring Batch )使用執行緒來管理資料。同時操作執行緒或CPU程序可提高效能,從而實現更快,更高效的程式。
獲取原始碼
獲取 此Java Challenger 的程式碼 。你可以在按照示例操作時執行自己的測試。
找到你的第一個執行緒:Java的main()方法
即使你從未直接使用Java執行緒,你也間接使用它們,因為Java的 main()方法 包含一個 主執行緒 。無論何時執行該main()方法,你都執行了主執行緒。學習Thread該類對於理解執行緒在Java程式中的工作方式非常有幫助。我們可以通過呼叫、currentThread().getName()方法來訪問正在執行的執行緒,如下所示:
public class MainThread { public static void main(String... mainThread) { System.out.println(Thread.currentThread().getName()); } }
此程式碼將列印“main”,標識當前正在執行的執行緒。知道如何識別正在執行的執行緒是吸收執行緒概念的第一步。
Java執行緒生命週期
使用執行緒時,瞭解執行緒狀態至關重要。Java執行緒生命週期包含六種執行緒狀態:
· New :例項化了一個新的Thread()。
· Runnable :Thread的start()方法被呼叫。
· Running :start()已呼叫並且執行緒正在執行。
· Suspended :執行緒暫時掛起,可以由另一個執行緒恢復。
· Blocked :執行緒正在等待機會執行。當一個執行緒已經呼叫synchronized()方法,下一個執行緒必須等到它完成,就會發生這種情況。
· Terminated :執行緒執行完成。

圖1. Java執行緒生命週期的六種狀態還有更多關於執行緒狀態的探索和理解,但圖1中的資訊足以讓你解決這個Java挑戰。
併發處理:擴充套件Thread類
最簡單的是,通過擴充套件Thread類來完成併發處理,如下所示。
public class InheritingThread extends Thread { InheritingThread(String threadName) { super(threadName); } public static void main(String... inheriting) { System.out.println(Thread.currentThread().getName() + " is running"); new InheritingThread("inheritingThread").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } }
在這裡,我們執行兩個執行緒:MainThread和InheritingThread。當我們使用new inheritingThread()呼叫方法start()時,將run()執行方法中的邏輯。我們還在Thread類建構函式中傳遞第二個執行緒的名稱,因此輸出將是:
main is running. inheritingThread is running.
Runnable介面
你可以實現 Runnable 介面,而不是使用繼承。通過Runnable在Thread建構函式內傳遞會導致更少的耦合和更大的靈活性。傳遞Runnable之後,我們可以start()像上一個示例中那樣呼叫方法:
public class RunnableThread implements Runnable { public static void main(String... runnableThread) { System.out.println(Thread.currentThread().getName()); new Thread(new RunnableThread()).start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
非守護執行緒vs守護執行緒
在執行方面,有兩種型別的執行緒: · 非守護執行緒執行 直到結束。主執行緒是非守護程式執行緒的一個很好的例子。main()除非System.exit()強制程式完成,否則程式碼將始終執行到最後。 · 守護執行緒 是相反的,當所有非守護執行緒執行結束,那守護執行緒也退出了。為了更好地理解守護和非守護執行緒的關係,請研究此示例:
import java.util.stream.IntStream; public class NonDaemonAndDaemonThread { public static void main(String... nonDaemonAndDaemon) throws InterruptedException { System.out.println("Starting the execution in the Thread " + Thread.currentThread().getName()); Thread daemonThread = new Thread(() -> IntStream.rangeClosed(1, 100000).forEach(System.out::println)); daemonThread.setDaemon(true); daemonThread.start(); Thread.sleep(10); System.out.println("End of the execution in the Thread " + Thread.currentThread().getName()); } }
在這個例子中,我使用了守護程式執行緒來宣告1到100,000的範圍,迭代所有這些,然後列印。但請記住,如果非守護程序的主執行緒首先完成,守護程式執行緒將無法完成執行。輸出將按如下方式進行:1. 在主執行緒中開始執行。2. 列印數字從1到100,000。3. 主執行緒中的執行結束,很可能在迭代到100,000之前完成。最終輸出將取決於你的JVM實現。這讓我想到了下一點:執行緒是不可預測的。
執行緒優先順序和JVM
可以使用該setPriority方法確定執行緒執行的優先順序,但是如何處理它取決於JVM實現。Linux,MacOS和Windows都有不同的JVM實現,每個都將根據自己的預設值處理執行緒優先順序。但是,你設定的執行緒優先順序確實會影響執行緒呼叫的順序。在Thread類宣告的三個常數是:
/** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
嘗試對以下程式碼執行一些測試,以檢視最終的執行優先順序:
public class ThreadPriority { public static void main(String... threadPriority) { Thread moeThread = new Thread(() -> System.out.println("Moe")); Thread barneyThread = new Thread(() -> System.out.println("Barney")); Thread homerThread = new Thread(() -> System.out.println("Homer")); moeThread.setPriority(Thread.MAX_PRIORITY); barneyThread.setPriority(Thread.NORM_PRIORITY); homerThread.setPriority(Thread.MIN_PRIORITY); homerThread.start(); barneyThread.start(); moeThread.start(); } }
即使我們設定moeThread為MAX_PRIORITY,我們也不能指望首先執行此執行緒。相反,執行順序將是隨機的。
常數與列舉
這個Thread類是用Java 1.0引入的。那時,優先順序是使用常量而不是列舉來設定的。但是,使用常量存在問題:如果我們傳遞的優先順序數不在1到10的範圍內,則該setPriority()方法將丟擲 IllegalArgumentException 。今天,我們可以使用列舉來解決這個問題。使用列舉使得無法傳遞非法引數,這既簡化了程式碼又使我們能夠更好地控制其執行。
Java執行緒挑戰!
你已經瞭解了一些關於執行緒的知識,但這對於這篇文章的Java挑戰來說已經足夠了。首先,研究以下程式碼:
public class ThreadChallenge { private static int wolverineAdrenaline = 10; public static void main(String... doYourBest) { new Motorcycle("Harley Davidson").start(); Motorcycle fastBike = new Motorcycle("Dodge Tomahawk"); fastBike.setPriority(Thread.MAX_PRIORITY); fastBike.setDaemon(false); fastBike.start(); Motorcycle yamaha = new Motorcycle("Yamaha YZF"); yamaha.setPriority(Thread.MIN_PRIORITY); yamaha.start(); } static class Motorcycle extends Thread { Motorcycle(String bikeName) { super(bikeName); } @Override public void run() { wolverineAdrenaline++; if (wolverineAdrenaline == 13) { System.out.println(this.getName()); } } } }
這段程式碼的輸出是什麼?分析程式碼並根據你學到的內容嘗試確定自己的答案。
A. Harley Davidson
B. Dodge Tomahawk
C. Yamaha YZF
D. Indeterminate
剛剛發生了什麼?瞭解執行緒行為
在上面的程式碼中,我們建立了三個執行緒。第一個執行緒是Harley Davidson,我們為此執行緒分配了預設優先順序。Dodge Tomahawk分配了第二個執行緒MAX_PRIORITY。第三是Yamaha YZF,與MIN_PRIORITY。
然後我們啟動了執行緒。為了確定執行緒將執行的順序,你可能首先注意到Motorcycle類擴充套件了Thread類,並且我們已經在建構函式中傳遞了執行緒名稱。我們還用條件覆蓋了run()方法:if wolverineAdrenaline is equals to 13。即使它Yamaha YZF是我們執行順序中的第三個執行緒,且MIN_PRIORITY不能保證它將在所有JVM實現的最後執行。
你可能還會注意到,在此示例中,我們將Dodge Tomahawk執行緒設定為daemon。因為它是一個守護程式執行緒,Dodge Tomahawk可能永遠不會完成執行。但是其他兩個執行緒預設是非守護程序,因此Harley Davidson和Yamaha YZF執行緒肯定會完成它們的執行。
總之,結果將是D:Indeterminate,因為無法保證執行緒排程程式將遵循我們的執行順序或執行緒優先順序。請記住,我們不能依賴程式邏輯(執行緒或執行緒優先順序的順序)來預測JVM的執行順序。
Java執行緒常見錯誤
- · 呼叫該run()方法以嘗試啟動新執行緒。
- · 試圖啟動一個執行緒兩次(這將導致一個IllegalThreadStateException)。
- · 允許多個程序在不應更改狀態時更改物件的狀態。
- · 編寫依賴於執行緒優先順序的程式邏輯(你無法預測它)。
- · 依賴於執行緒執行的順序 - 即使我們首先啟動一個執行緒,也不能保證它將首先被執行。
關於Java執行緒要記住什麼
- · 呼叫start()方法啟動Thread。
- · 可以直接擴充套件Thread類以使用執行緒。
- · 可以在Runnable介面內實現執行緒動作。
- · 執行緒優先順序取決於JVM實現。
- · 執行緒行為將始終取決於JVM實現。
- · 如果非守護程式執行緒首先結束,則守護程式執行緒將無法完成。