Java多線程簡析——Synchronized(同步鎖)、Lock以及線程池

Java多線程

Java中,可運行的程序都是有一個或多個進程組成。進程則是由多個線程組成的。

最簡單的一個進程,會包括mian線程以及GC線程。

爲什麼要使用同步鎖?

  在《Thinking in Java》中,是這麼說的:對於併發工作,你需要某種方式來防止兩個任務訪問相同的資源(其實就是共享資源競爭)。 防止這種衝突的方法就是當資源被一個任務使用時,在其上加鎖。第一個訪問某項資源的任務必須鎖定這項資源,使其他任務在其被解鎖之前,就無法訪問它了,而在其被解鎖之時,另一個任務就可以鎖定並使用它了。
 
   基本上所有的併發模式在解決線程衝突問題的時候,都是採用序列化訪問共享資源的方案。這意味在給定時刻只允許一個任務訪問共享資源,通常這是通過在代碼前面加上一條鎖語句來實現的,鎖語句產生了一種互相排斥的效果,這種機制稱爲互斥量(mutex)。

什麼時候使用同步鎖呢?

  Brian同步規則:如果你正在寫一個變量,它可能接下來將被另一個線程讀取,或者正在讀取一個上一次已經被另一個線程寫過的變量,那麼你必須使用同步,並且,讀寫線程都必須用相同的監視器鎖同步。

  注意:每個訪問臨界共享資源的方法都必須被同步,否則它們不會正確工作。
 

 如何使用同步鎖呢?

synchronized 關鍵字,它包括兩種用法:synchronized 方法和 synchronized 塊。 

  • synchronized 方法:

public synchronized void countNum(int n);

特定對象所有synchronized方法共享同一個鎖,這種機制確保了同一時刻對於每一個類實例,其所有聲明爲 synchronized 的成員函數中至多隻有一個處於可執行狀態(因爲至多隻有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要所有可能訪問類成員變量的方法均被聲明爲 synchronized)。 

  不光如此,靜態方法也可以聲明爲 synchronized ,以控制其對類的靜態成員變量的訪問。

public static synchronized void countNum(int n);

synchronized 方法的缺陷:若將一個大的方法聲明爲synchronized 將會大大影響效率。

  典型地,若將線程類的方法 run() 聲明爲synchronized ,由於在線程的整個生命期內它一直在運行,因此將導致它對本類任何synchronized 方法的調用都永遠不會成功。當然我們可以通過將訪問類成員變量的代碼放到專門的方法中,將其聲明爲synchronized ,並在主方法中調用來解決這一問題,但是 Java 爲我們提供了更好的解決辦法,那就是 synchronized 塊。

  • synchronized 塊:

synchronized(SyncObject.Class) { 
    //允許訪問控制的代碼 
} 

synchronized 塊是這樣一個代碼塊,其中的代碼必須獲得對象 syncObject (如前所述,可以是類實例或類)的鎖方能執行,具體機制同前所述。由於可以針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。

  在使用synchronized 塊的時候,一定要遵循Brian同步規則,並對每個訪問臨界共享資源的方法都進行同步。



在圖中,紅框標識的部分方法,可以認爲已過時,不再使用。

(1)wait、notify、notifyAll是線程中通信可以使用的方法。線程中調用了wait方法,則進入阻塞狀態,只有等另一個線程調用與wait同一個對象的notify方法。這裏有個特殊的地方,調用wait或者notify,前提是需要獲取鎖,也就是說,需要在同步塊中做以上操作。

(2)join方法。該方法主要作用是在該線程中的run方法結束後,才往下執行。如以下代碼:

package com.thread.simple;  
  
public class ThreadJoin {  
  
      
    public static void main(String[] args) {  
  
        Thread thread= new Thread(new Runnable() {  
              
            @Override  
            public void run() {  
                System.err.println("線程"+Thread.currentThread().getId()+" 打印信息");  
            }  
        });  
        thread.start();  
          
        try {  
            thread.join();  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
          
        System.err.println("主線程打印信息");  
          
    }  
  
}  
該方法顯示的信息是:

線程8 打印信息

主線程打印信息

如果去掉其中的join方法,則顯示如下:

主線程打印信息
線程8 打印信息

(3)yield方法。這個是線程本身的調度方法,使用時你可以在run方法執行完畢時,調用該方法,告知你已可以出讓內存資源。

其他的線程方法,基本都會在日常中用到,如start、run、sleep,這裏就不再介紹。





發佈了27 篇原創文章 · 獲贊 32 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章