Java 多線程設計模式之基礎概念

順序、併發與並行

順序

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

並行

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

併發

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

總結

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

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


線程啓動與中止

啓動方式

  • 利用 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 時的更爲健壯。

參考


作者:騎摩托馬斯
鏈接:https://juejin.im/post/5b9f6b515188255c877e37f8
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章