【JavaSE】Lock鎖和synchronized鎖的比較,lock鎖的特性,讀寫鎖的實現。

馮某r發表於2019-01-22

一、為什麼出現了Lock鎖

出現lock鎖肯定是由於synchronized鎖有一些缺陷,下面說一下具體缺陷:

1. 不提供中斷鎖

synchronized鎖有兩種情況會釋放鎖:
①.程式碼塊或者方法執行完畢,自動釋放鎖
②.在執行程式碼塊或者方法過程中發生異常,自動釋放鎖.
那麼如果在執行過程中要等待IO或者其他原因被阻塞了,但是又沒有其他方法釋放鎖,其他執行緒只能繼續等待,這是非常影響程式效率的。
lock鎖提供了一些超時中斷和響應中斷的方法,讓等待獲取鎖的執行緒不會傻傻的等下去,可以幹一些別的事情

2.不能很好的實現讀寫操作

同時進行讀操作不需要互斥,讀寫操作和寫寫操作應該被互斥。但是用synchronized鎖的時候,讀操作和讀操作也沒互斥了,這樣就達不到我們的預期效果。但是lock鎖卻可以很好的實現讀寫操作。

需要注意

lock鎖和synchronized鎖最大的不同在於,內檢鎖是自動釋放鎖的,而lock鎖是需要手動呼叫方法去釋放鎖的,所以為了防止發生異常中斷而鎖卻沒有釋放,lock鎖的釋放一般都放在finally程式碼塊中。

二、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()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行等待。
  • lockInterruptibly()方法可以讓等待的執行緒響應中斷,如果執行緒A和執行緒B同時競爭鎖,執行緒B沒有搶到鎖進行等待,這時候對執行緒B呼叫threadB.interrupt()方法能夠中斷B的等待過程。
  • tryLock()方法是嘗試獲取鎖,如果當前鎖是空閒的,或者當前執行緒持有鎖(可重入),就返回true表示當前執行緒獲取到鎖,否則就返回false,不會進行等待。
  • tryLock(long time, TimeUnit unit)方法是在tryLock()方法的基礎上加入了一個等待時間作為引數,它沒有獲取到鎖會等待一定時間,如果超過這個時間還沒有獲取到鎖就返回false。
  • unlock()是進行鎖的釋放,注意重入鎖幾次,就要釋放幾次。

三、實現Lock介面的子類

我們一般使用的兩個子類是:ReentrantLock鎖和ReentrantReadWriteLock鎖

1.ReentrantLock鎖

ReentrantLock是和synchronized鎖一樣的互斥鎖,具有相同的功能,但是有更好的擴充套件性。

①.構造方法

看一個類先看構造方法:

public ReentrantLock() {
        sync = new NonfairSync();
    }

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

有兩個構造方法,可以看到無參的時候,預設使用的是非公平的鎖。而傳入引數是true的話,使用的就是公平鎖了。
公平鎖:當鎖被釋放的時候,在所有等待鎖的執行緒裡面選用等待時間最長的執行緒。
非公平鎖:選取執行緒沒有規則。這樣線上程數量非常多的時候,有可能導致有的執行緒無限的等待下去。內建鎖使用的就是非公平鎖

②實現介面的方法

public void lock() {
        sync.lock();
    }
public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
public void unlock() {
        sync.release(1);
    }
public Condition newCondition() {
        return sync.newCondition();
    }

通過原始碼發現,每一個介面的方法,都是呼叫了sync變數的方法,而sync變數是在呼叫ReentrantLock構造方法的時候(假設呼叫無參構造),由NonfairSync()例項化而來。NonfairSync()又繼承了Sync類。

static final class NonfairSync extends Sync

Sync類是ReentrantLock類的一個靜態內部類:

abstract static class Sync extends AbstractQueuedSynchronizer

Sync繼承了AbstractQueuedSynchronizer,簡稱AQS它是一個同步器,AQS定義了一套多執行緒訪問共享資源的同步器框架,許多同步類實現都依賴於它。在下一篇部落格我會講解一下Lock鎖的原始碼是如何結合AQS實現的。

③.擴充套件的方法。

除了實現介面的方法,ReentrantLock類擴充套件了一些方法,包括檢視是否有執行緒在排隊等待鎖,檢視某個執行緒是否在排隊等待鎖,檢視是否有執行緒在等待佇列中,返回正在等待獲取此鎖執行緒數,和返回所有正等待獲取此鎖的執行緒。

1.ReentrantReadWriteLock鎖

這是一個介面:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將檔案的讀寫操作分開,分成2個鎖來分配給執行緒,從而使得多個執行緒可以同時進行讀操作。下面的ReentrantReadWriteLock實現了ReadWriteLock介面。

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable

這個類的方法也是依靠Sync內部靜態類來實現的,不過ReentrantReadWriteLock可以實現讀鎖和讀鎖的執行緒不互斥,而讀鎖和寫鎖、寫鎖和寫鎖的執行緒之間互斥的特性。

四、Lock和synchronized的選擇

總結來說,Lock和synchronized有以下幾點不同:
  1)Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;
  2)synchronized在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;
  3)Lock可以讓等待鎖的執行緒響應中斷,而synchronized卻不行,使用synchronized時,等待的執行緒會一直等待下去,不能夠響應中斷;
  4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
  5)Lock可以提高多個執行緒進行讀操作的效率。
  在效能上來說,如果競爭資源不激烈,兩者的效能是差不多的,而當競爭資源非常激烈時(即有大量執行緒同時競爭),此時Lock的效能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

相關文章