JVM中的執行緒行為 -JavaWorld
執行緒化是指同時執行程式設計過程以提高應用程式效能的實踐。雖然直接在業務應用程式中使用執行緒並不常見,但它們一直在Java框架中使用。
例如,處理大量資訊的框架(如 Spring Batch )使用執行緒來管理資料。同時操作執行緒或CPU程序可提高效能,從而實現更快,更高效的程式。
找到你的第一個執行緒:Java的main()方法
即使你從未直接使用Java執行緒,你也間接使用它們,因為Java的 main()方法 包含一個 主執行緒 。無論何時執行該main()方法,您都執行了main Thread。
學習Thread該類對於理解執行緒在Java程式中的工作方式非常有幫助。我們可以通過呼叫currentThread().getName()方法來訪問正在執行的執行緒,如下所示:
<b>public</b> <b>class</b> MainThread { <b>public</b> <b>static</b> <b>void</b> 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類來完成併發處理,如下所示。
<b>public</b> <b>class</b> InheritingThread <b>extends</b> Thread { InheritingThread(String threadName) { <b>super</b>(threadName); } <b>public</b> <b>static</b> <b>void</b> main(String... inheriting) { System.out.println(Thread.currentThread().getName() + <font>" is running"</font><font>); <b>new</b> InheritingThread(</font><font>"inheritingThread"</font><font>).start(); } @Override <b>public</b> <b>void</b> run() { System.out.println(Thread.currentThread().getName() + </font><font>" is running"</font><font>); } } </font>
在這裡,我們執行兩個執行緒:MainThread和InheritingThread。當我們start()使用new 呼叫方法時inheritingThread(),將run()執行方法中的邏輯。
我們還在Thread類建構函式中傳遞第二個執行緒的名稱,因此輸出將是:
main is running. inheritingThread is running.
Runnable介面
您可以實現 Runnable 介面,而不是使用繼承。Runnable在Thread建構函式內部傳遞會導致更少的耦合和更大的靈活性。傳遞之後Runnable,我們可以start()像上一個示例中那樣呼叫方法:
<b>public</b> <b>class</b> RunnableThread implements Runnable { <b>public</b> <b>static</b> <b>void</b> main(String... runnableThread) { System.out.println(Thread.currentThread().getName()); <b>new</b> Thread(<b>new</b> RunnableThread()).start(); } @Override <b>public</b> <b>void</b> run() { System.out.println(Thread.currentThread().getName()); } }
非守護程序vs守護程序執行緒
在執行方面,有兩種型別的執行緒:
- 執行非守護程式執行緒會一直到結束。主執行緒本身就是非守護程式執行緒的一個很好的例子。main()除非System.exit()強制程式完成,否則程式碼輸入將始終執行到最後。
- 一個守護執行緒是相反的,是一個不需要一直執行到結束的處理程式。
請記住規則:如果封閉的非守護程式執行緒在守護程式執行緒之前結束,則守護程式執行緒將在結束之前執行。為了更好地理解守護程序和非守護程序執行緒的關係,請研究此示例:
<b>import</b> java.util.stream.IntStream; <b>public</b> <b>class</b> NonDaemonAndDaemonThread { <b>public</b> <b>static</b> <b>void</b> main(String... nonDaemonAndDaemon) throwsInterruptedException { System.out.println(<font>"Starting the execution in the Thread "</font><font> +Thread.currentThread().getName()); Thread daemonThread = <b>new</b> Thread(() ->IntStream.rangeClosed(1, 100000) .forEach(System.out::println)); daemonThread.setDaemon(<b>true</b>); daemonThread.start(); Thread.sleep(10); System.out.println(</font><font>"End of the execution in the Thread "</font><font> + Thread.currentThread().getName()); } } </font>
在這個例子中,我使用了守護程式執行緒來宣告1到100,000的範圍,迭代所有這些,然後列印。但請記住,如果非守護程序的主執行緒首先完成,守護程式執行緒將無法完成執行。
輸出將按如下方式進行:
- 在主執行緒中開始執行。
- 列印數字從1到100,000。
- 主執行緒中的執行結束,很可能在迭代到100,000之前完成。
最終輸出將取決於您的JVM實現。
這讓我想到了下一點:執行緒是不可預測的。
執行緒優先順序和JVM
可以使用該setPriority方法確定執行緒執行的優先順序,但是如何處理它取決於JVM實現。Linux,MacOS和Windows都有不同的JVM實現,每個都將根據自己的預設值處理執行緒優先順序。
但是,您設定的執行緒優先順序確實會影響執行緒呼叫的順序。在Thread類上宣佈的三個常數是:
<font><i>/** * The minimum priority that a thread can have. */</i></font><font> <b>public</b> <b>static</b> <b>final</b> <b>int</b> MIN_PRIORITY = 1; </font><font><i>/** * The default priority that is assigned to a thread. */</i></font><font> <b>public</b> <b>static</b> <b>final</b> <b>int</b> NORM_PRIORITY = 5; </font><font><i>/** * The maximum priority that a thread can have. */</i></font><font> <b>public</b> <b>static</b> <b>final</b> <b>int</b> MAX_PRIORITY = 10; </font>
嘗試對以下程式碼執行一些測試,以檢視最終的執行優先順序:
<b>public</b> <b>class</b> ThreadPriority { <b>public</b> <b>static</b> <b>void</b> main(String... threadPriority) { Thread moeThread = <b>new</b> Thread(() -> System.out.println(<font>"Moe"</font><font>)); Thread barneyThread = <b>new</b> Thread(() -> System.out.println(</font><font>"Barney"</font><font>)); Thread homerThread = <b>new</b> Thread(() -> System.out.println(</font><font>"Homer"</font><font>)); moeThread.setPriority(Thread.MAX_PRIORITY); barneyThread.setPriority(Thread.NORM_PRIORITY); homerThread.setPriority(Thread.MIN_PRIORITY); homerThread.start(); barneyThread.start(); moeThread.start(); } } </font>
即使我們設定moeThread為MAX_PRIORITY,我們也不能指望首先執行此執行緒。相反,執行順序將是隨機的。
Java執行緒挑戰!
你已經瞭解了一些關於執行緒的知識,但這對於這篇文章的Java挑戰來說已經足夠了。
首先,研究以下程式碼:
<b>public</b> <b>class</b> ThreadChallenge { <b>private</b> <b>static</b> <b>int</b> wolverineAdrenaline = 10; <b>public</b> <b>static</b> <b>void</b> main(String... doYourBest) { <b>new</b> Motorcycle(<font>"Harley Davidson"</font><font>).start(); Motorcycle fastBike = <b>new</b> Motorcycle(</font><font>"Dodge Tomahawk"</font><font>); fastBike.setPriority(Thread.MAX_PRIORITY); fastBike.setDaemon(false); fastBike.start(); Motorcycle yamaha = <b>new</b> Motorcycle(</font><font>"Yamaha YZF"</font><font>); yamaha.setPriority(Thread.MIN_PRIORITY); yamaha.start(); } <b>static</b> <b>class</b> Motorcycle <b>extends</b> Thread { Motorcycle(String bikeName) { <b>super</b>(bikeName); } @Override <b>public</b> <b>void</b> run() { wolverineAdrenaline++; <b>if</b> (wolverineAdrenaline == 13) { System.out.println(<b>this</b>.getName()); } } } } </font>
這段程式碼的輸出是什麼?分析程式碼並根據您學到的內容嘗試確定自己的答案。
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實現。
- 如果封閉的非守護程式執行緒首先結束,則守護程式執行緒將無法完成。