Java 多執行緒設計模式之基礎概念

騎摩托馬斯發表於2018-09-17

順序、併發與並行

順序

用於表示多個操作“依次處理”。比如把十個操作交給一個人來處理時,這個人要一個一個地按順序來處理

並行

用於標識多個操作“同時處理”。比如十個操作分給兩個人處理時,這兩個人就會並行來處理。

併發

相對於順序和並行來說比較抽象,用於表示“將一個操作分割成多個部分並且允許無序處理”。比如將十個操作分成相對獨立的兩類,這樣便能夠開始併發處理了。如果一個人來處理,這個人就是順序處理分開的併發操作,而如果是兩個人,這兩個人就可以並行處理同一個操作。

總結

多執行緒程式都是併發處理的。如果 CPU 只有一個,那麼併發處理就是順序執行的,而如果有多個 CPU,那麼併發處理就可能會並行執行。

併發處理的順序執行與併發處理的並行執行示意圖如下所示

Java 多執行緒設計模式之基礎概念

執行緒啟動與中止

啟動方式

  • 利用 Thread 類的子類的例項啟動執行緒
  • 利用 Runnable 介面的實現類的例項啟動執行緒

以上兩種方式都需要使用 start 方法用於啟動新的執行緒,在此需要注意的事情是,啟動新執行緒呼叫的是 start 方法而不是 run 方法

終止

直到所有的執行緒都終止後,程式才會終止。也就是說,當這兩個執行緒都終止後,程式才會終止。

Java 程式的終止是指除守護執行緒以外的執行緒全部終止。守護執行緒是執行後臺作業的執行緒。我們可以通過 setDaemon 方法把執行緒設定為守護執行緒。

小知識

java.util.concurrent 包中包含一個將執行緒建立抽象化的 ThreadFactory 介面。利用該介面,我們可以將 Runnable 作為傳入引數並通過 new 建立 Thread 例項的處理隱藏在 ThreadFactory 內部。

Executors 類中含有多種建立 ThreadFactory 的方法,感興趣的可以去看一下原始碼

synchronized 相關

synchronized 方法

如果宣告一個方法時,在前面加上關鍵字 synchronized 那麼這個方法就只能由一個執行緒執行。只能由一個執行緒執行是每次只能由一個執行緒執行的意思,並不是說僅能讓某一特定執行緒執行。這種方法叫做 synchronized,有時也稱為同步方法。

    synchronized void method() {
        ...
    }
複製程式碼

synchronized 程式碼塊

如果只是想讓方法中的某一部分由一個執行緒執行,而非整個方法,則可使用 synchronized 程式碼塊

    synchronized (表示式) {
        ...
    }
複製程式碼

synchronized 例項方法和 synchronized 程式碼塊

假設有如下 synchronized 例項方法

    synchronized void method() {
        ...
    }
複製程式碼

這跟下面將方法體用 synchronized 程式碼塊包圍起來是等效的

void method() {
    synchronized (this) {
        ...
    }
}
複製程式碼

synchronized 例項方法是使用 this 的鎖來執行執行緒的互斥處理的

synchronized 靜態方法和 synchronized 程式碼塊

synchronized 靜態方法和 synchronized 例項方法是相同的。但是 synchronized 靜態方法使用的鎖和 synchronized 例項方法使用的鎖是不一樣的

class Something {
    static synchronized void method() {
        ...
    }
}
複製程式碼

這跟下面將方法體用 synchronized 程式碼塊包圍起來是等效的

class Something {
    static void method() {
        synchronized (Something.class) {
            ...
        }
    }
}
複製程式碼

synchronized 靜態方法是使用該類的類物件鎖來執行執行緒的互斥處理的。 Something.class 是 Something 類對應的 java.lang.class 類的例項

wait、notify 和 notifyAll

等待佇列

所有例項都擁有一個等待佇列,它是在例項的 wait 方法執行後停止操作的執行緒佇列。就好比為每個例項準備的執行緒休息室

在執行 wait 方法後,執行緒便會暫停操作,進入等待佇列這個休息室。除非發生下列某一情況,否則執行緒會一直在等待佇列中休眠。

  • 有其他執行緒的 notify 方法來喚醒執行緒
  • 有其他執行緒的 notifyAll 方法來喚醒執行緒
  • 有其他執行緒的 interrupt 方法來喚醒執行緒
  • wait 方法超時

若要執行 wait 方法,執行緒必須持有鎖。但如果執行緒進入等待佇列,便會釋放其例項的鎖

notify 方法

該方法會將等待佇列中的一個執行緒去除。同 wait 方法一樣,若要執行 notify 方法,執行緒也必須持有要呼叫的例項的鎖。

notify 喚醒的執行緒並不會在執行 notify 的一瞬間就重新執行。因為在執行 notify 的那一瞬間,執行 notify 的執行緒還持有著鎖,所以其他執行緒還無法獲取這個例項的鎖

notifyAll 方法

notify 方法僅喚醒一個執行緒,而 notifyAll 則喚醒所有執行緒,這是兩者之間唯一的區別

同 wait 方法和 notify 方法一樣, notifyAll 方法也只能由持有要呼叫的例項鎖的執行緒呼叫

notify 和 notifyAll 選擇

notify 方法和 notifyAll 方法非常相似,到底該使用哪個?

實際上,這很難選擇,由於 notify 喚醒的執行緒較少,所以處理速度要比使用 notifyAll 時快。 但使用 notify 時,如果處理不好,程式便可能會停止。一般來說,使用 notifyAll 時的程式碼要比使用 notify 時的更為健壯。

參考

相關文章