5分鐘搞清楚Synchronized和Lock的概念與區別

前言

併發編程中,鎖是經常需要用到的,今天我們一起來看下Java中的鎖機制:synchronized和lock。

v2-3f886e8103271b49f80e3a3f2c631289_hd.png

Synchronized 和 Lock的概念

Synchronized 是Java 併發編程中很重要的關鍵字,另外一個很重要的是 volatile。Syncronized 的目的是一次只允許一個線程進入由他修飾的代碼段,從而允許他們進行自我保護。Synchronized 很像生活中的鎖例子,進入由Synchronized 保護的代碼區首先需要獲取 Synchronized 這把鎖,其他線程想要執行必須進行等待。Synchronized 鎖住的代碼區域執行完成後需要把鎖歸還,也就是釋放鎖,這樣才能夠讓其他線程使用。


Lock 是 Java併發編程中很重要的一個接口,它要比 Synchronized 關鍵字更能直譯"鎖"的概念,Lock需要手動加鎖和手動解鎖,一般通過 lock.lock() 方法來進行加鎖, 通過 lock.unlock() 方法進行解鎖。與 Lock 關聯密切的鎖有 ReetrantLock 和 ReadWriteLock。


ReetrantLock 實現了Lock接口,它是一個可重入鎖,內部定義了公平鎖與非公平鎖。


ReadWriteLock 一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。ReentrantReadWirteLock實現了ReadWirteLock接口,並未實現Lock接口。

v2-2f6a4e3955172b29ca85b0c2511cbbfe_hd.png

Synchronized 和 Lock 的使用

Synchronized 和 Lock 的使用:

下面是 Synchronized 的例子:

在方法上使用 Synchronized

方法聲明時使用,放在範圍操作符之後,返回類型聲明之前。即一次只能有一個線程進入該方法,其他線程要想在此時調用該方法,只能排隊等候。

private int number;
public synchronized void numIncrease(){
  number++;
}

在某個代碼段使用 Synchronized

你也可以在某個代碼塊上使用 Synchronized 關鍵字,表示只能有一個線程進入某個代碼段。

public void numDecrease(Object num){
  synchronized (num){
    number++;
  }
}

使用 Synchronized 鎖住整個對象

synchronized後面括號裏是一對象,此時線程獲得的是對象鎖。

public void test() {
  synchronized (this) {
    // ...
  }
}

下面是 Lock 的例子:

Lock是一個接口,它主要由下面這幾個方法

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

對上面 Lock 接口的方法做一個簡單的解釋:

lock(): lock 方法可能是平常使用最多的一個方法,就是用來獲取鎖。如果鎖被其他線程獲取,則進行等待。

如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。

Lock lock = ...;
lock.lock();
try{
    //處理任務
}catch(Exception ex){
     
}finally{
    lock.unlock();   //釋放鎖
}

tryLock() :方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。

tryLock(long time, TimeUnit unit) 方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。

Lock lock = ...;
if(lock.tryLock()) {
     try{
         //處理任務
     }catch(Exception ex){
         
     }finally{
         lock.unlock();   //釋放鎖
     }
}else {
    //如果不能獲取鎖,則直接做其他事情
}

lockInterruptibly() : 此方法比較特殊,當通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。也就是說,當兩個線程同時通過 lock.lockInterruptibly() 想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那麼對線程B調用 threadB.interrupt() 方法能夠中斷線程B的等待過程。

由於 lockInterruptibly() 的聲明中拋出了異常,所以 lock.lockInterruptibly() 必須放在try塊中或者在調用lockInterruptibly() 的方法外聲明拋出 InterruptedException。一般形式如下:

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {
     //.....
    }
    finally {
        lock.unlock();
    }
}

一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。

注意,當一個線程獲取了鎖之後,是不會被interrupt()方法中斷的。因爲本身在前面的文章中講過單獨調用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。因此當通過lockInterruptibly()方法獲取某個鎖時,如果不能獲取到,只有進行等待的情況下,是可以響應中斷的。而用synchronized修飾的話,當一個線程處於等待某個鎖的狀態,是無法被中斷的,只有一直等待下去。

歡迎大家關注我的公種浩【程序員追風】,文章都會在裏面更新,整理的資料也會放在裏面。


v2-d99d1e4f095e2a0ef66dedaad8367946_hd.png

Synchronized 和 Lock 的主要區別

Synchronzied 和 Lock 的主要區別如下:

  • 存在層面:Syncronized 是Java 中的一個關鍵字,存在於 JVM 層面,Lock 是 Java 中的一個接口

  • 鎖的釋放條件:1. 獲取鎖的線程執行完同步代碼後,自動釋放;2. 線程發生異常時,JVM會讓線程釋放鎖;Lock 必須在 finally 關鍵字中釋放鎖,不然容易造成線程死鎖

  • 鎖的獲取: 在 Syncronized 中,假設線程 A 獲得鎖,B 線程等待。如果 A 發生阻塞,那麼 B 會一直等待。在 Lock 中,會分情況而定,Lock 中有嘗試獲取鎖的方法,如果嘗試獲取到鎖,則不用一直等待

  • 鎖的狀態:Synchronized 無法判斷鎖的狀態,Lock 則可以判斷

  • 鎖的類型:Synchronized 是可重入,不可中斷,非公平鎖;Lock 鎖則是 可重入,可判斷,可公平鎖

  • 鎖的性能:Synchronized 適用於少量同步的情況下,性能開銷比較大。Lock 鎖適用於大量同步階段:

  • Lock 鎖可以提高多個線程進行讀的效率(使用 readWriteLock)

  • 在競爭不是很激烈的情況下,Synchronized的性能要優於ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常態;

  • ReetrantLock 提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。


最後

 歡迎大家一起交流,喜歡文章記得點個贊喲,感謝支持!


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