程式設計思想之多執行緒與多程式(3):Java 中的多執行緒

發表於2015-10-26

程式設計思想之多執行緒與多程式(1)——以作業系統的角度述說執行緒與程式》一文詳細講述了執行緒、程式的關係及在作業系統中的表現,這是多執行緒學習必須瞭解的基礎。本文將接著講一下Java中多執行緒程式的開發。

 

單執行緒

任何程式至少有一個執行緒,即使你沒有主動地建立執行緒,程式從一開始執行就有一個預設的執行緒,被稱為主執行緒,只有一個執行緒的程式稱為單執行緒程式。如下面這一簡單的程式碼,沒有顯示地建立一個執行緒,程式從main開始執行,main本身就是一個執行緒(主執行緒),單個執行緒從頭執行到尾。

【Demo1】:單執行緒程式

 

建立執行緒

單執行緒程式簡單明瞭,但有時無法滿足特定的需求。如一個文書處理的程式,我在列印文章的同時也要能對文字進行編輯,如果是單執行緒的程式則要等印表機列印完成之後你才能對文字進行編輯,但列印的過程一般比較漫長,這是我們無法容忍的。如果採用多執行緒,列印的時候可以單獨開一個執行緒去列印,主執行緒可以繼續進行文字編輯。在程式需要同時執行多個任務時,可以採用多執行緒。

在程式需要同時執行多個任務時,可以採用多執行緒。Java給多執行緒程式設計提供了內建的支援,提供了兩種建立執行緒方法:1.通過實現Runable介面;2.通過繼承Thread類。

Thread是JDK實現的對執行緒支援的類,Thread類本身實現了Runnable介面,所以Runnable是顯示建立執行緒必須實現的介面; Runnable只有一個run方法,所以不管通過哪種方式建立執行緒,都必須實現run方法。我們可以看一個例子。

【Demo2】:執行緒的建立和使用

 

說明:上面的例子中例舉了兩種實現執行緒的方式。大部分情況下選擇實現Runnable介面的方式會優於繼承Thread的方式,因為:
1. 從 Thread 類繼承會強加類層次;
2. 有些類不能繼承Thread類,如要作為執行緒執行的類已經是某一個類的子類了,但Java只支援單繼承,所以不能再繼承Thread類了。

 

執行緒同步

執行緒與執行緒之間的關係,有幾種:

模型一:簡單的執行緒,多個執行緒同時執行,但各個執行緒處理的任務毫不相干,沒有資料和資源的共享,不會出現爭搶資源的情況。這種情況下不管有多少個執行緒同時執行都是安全的,其執行模型如下:

圖 1:處理相互獨立的任務

模型二:複雜的執行緒,多個執行緒共享相同的資料或資源,就會出現多個執行緒爭搶一個資源的情況。這時就容易造成資料的非預期(錯誤)處理,是執行緒不安全的,其模型如下:

圖 2:多個執行緒共享相同的資料或資源

在出現模型二的情況時就要考慮執行緒的同步,確保執行緒的安全。Java中對執行緒同步的支援,最常見的方式是新增synchronized同步鎖。

我們通過一個例子來看一下執行緒同步的應用。

買火車票是大家春節回家最為關注的事情,我們就簡單模擬一下火車票的售票系統(為使程式簡單,我們就抽出最簡單的模型進行模擬):有500張從北京到贛州的火車票,在8個視窗同時出售,保證系統的穩定性和資料的原子性。

圖 3:模擬火車票售票系統

【Demo3】:火車票售票系統模擬程式

 

測試程式:

 

結果如下:

在上面的例子中,涉及到資料的更改的Service類saleTicket方法和TicketSaler類run方法都用了synchronized同步鎖進行同步處理,以保證資料的準確性和原子性。

關於synchronized更詳細的用法請參見:《Java中Synchronized的用法

 

執行緒控制

在多執行緒程式中,除了最重要的執行緒同步外,還有其它的執行緒控制,如執行緒的中斷、合併、優先順序等。

執行緒等待(wait、notify、notifyAll)

  • Wait:使當前的執行緒處於等待狀態;
  • Notify:喚醒其中一個等待執行緒;
  • notifyAll:喚醒所有等待執行緒。

詳細用法參見:《 Java多執行緒中wait, notify and notifyAll的使用

執行緒中斷(interrupt)

在Java提供的執行緒支援類Thread中,有三個用於執行緒中斷的方法:

  • public void interrupt(); 中斷執行緒。
  • public static boolean interrupted(); 是一個靜態方法,用於測試當前執行緒是否已經中斷,並將執行緒的中斷狀態 清除。所以如果執行緒已經中斷,呼叫兩次interrupted,第二次時會返回false,因為第一次返回true後會清除中斷狀態。
  • public boolean isInterrupted(); 測試執行緒是否已經中斷。

【Demo4】:執行緒中斷的應用

呼叫程式碼:

結果:

執行緒合併(join)

所謂合併,就是等待其它執行緒執行完,再執行當前執行緒,執行起來的效果就好像把其它執行緒合併到當前執行緒執行一樣。其執行關係如下:

圖 4:執行緒合併的過程

public final void join()
等待該執行緒終止

public final void join(long millis);
等待該執行緒終止的時間最長為 millis 毫秒。超時為 0 意味著要一直等下去。

public final void join(long millis, int nanos)
等待該執行緒終止的時間最長為 millis 毫秒 + nanos 納秒

這個常見的一個應用就是安裝程式,很多大的軟體都會包含多個外掛,如果選擇完整安裝,則要等所有的外掛都安裝完成才能結束,且外掛與外掛之間還可能會有依賴關係。

【Demo5】:執行緒合併

 

合併執行緒的呼叫:

結果如下:

優先順序(Priority)

執行緒優先順序是指獲得CPU資源的優先程式。優先順序高的容易獲得CPU資源,優先順序底的較難獲得CPU資源,表現出來的情況就是優先順序越高執行的時間越多。

Java中通過getPriority和setPriority方法獲取和設定執行緒的優先順序。Thread類提供了三個表示優先順序的常量:MIN_PRIORITY優先順序最低,為1;NORM_PRIORITY是正常的優先順序;為5,MAX_PRIORITY優先順序最高,為10。我們建立執行緒物件後,如果不顯示的設定優先順序的話,預設為5。

【Demo】:執行緒優先順序

呼叫程式碼:

從結果中我們可以看到執行緒thread1明顯比執行緒thread3執行的快。

相關文章