Java併發編程之鎖機制之Lock接口

該文章屬於《Java併發編程》系列文章,若是想了解更多,請點擊《Java併發編程之總目錄》編程

前言

在上篇文章《Java併發編程之鎖機制之引導篇》及相關實現類,咱們大體瞭解了Lock接口(以及相關實現類)在併發編程重要做用。接下來咱們就來具體瞭解Lock接口中聲明的方法以及使用優點。bash

Lock簡介

Lock 接口實現類提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。此實現容許更靈活的結構,能夠具備差異很大的屬性,能夠支持多個相關的 Condition (Condition實現類ConditonObject來實現線程的通知/與喚醒機制,關於Condition後期會進行介紹)對象。多線程

鎖是用於控制多線程訪問共享資源的工具。一般,鎖提供對共享資源的獨佔訪問:一次只有一個線程能夠獲取鎖,對共享資源的全部訪問都須要首先獲取鎖。可是,一些鎖能夠容許同時訪問共享資源,例如ReadWriteLock併發

雖然使用關鍵字synchronized修飾的方法或代碼塊,會使得在監視器模式(ObjectMonitor)下編程變得很是容易(經過synchronized塊或者方法所提供的隱式獲取釋放鎖的便捷性)。雖然這種方式簡化了鎖的管理,可是某些狀況下,仍是建議採用Lock接口(及其相關子類)提供的顯示的鎖的獲取和釋放。例如,針對一個場景,手把手進行鎖獲取和釋放,先得到鎖A,而後再獲取鎖B,當鎖B得到後,釋放鎖A同時獲取鎖C,當鎖C得到後,再釋放B同時獲取鎖D,以此類推。這種場景下, synchronized關鍵字就不那麼容易實現了,而Lock接口的實現類容許鎖在不一樣的做用範圍內獲取和釋放,並容許以任何順序獲取和釋放多個鎖。ide

Lock接口中的方法

關於Lock接口中涉及到的方法具體以下:(建議直接在PC端查看,手機上有可能看的不是很清楚) 工具

lock_method.png
從上表中,咱們就能夠得出使用Lock接口實現的鎖機制與使用傳統的synchronized的區別

  1. 嘗試非阻塞地獲取鎖:當線程嘗試獲取鎖,若是這一時刻鎖沒有被其餘線程獲取到,則成功獲取並持有鎖。
  2. 能被中斷的獲取鎖:與synchronized不一樣,獲取到鎖的線程可以響應中斷,當獲取到鎖的線程被中斷時,中斷異常會被拋出,同時鎖也會被釋放。
  3. 超時獲取鎖:在指定的截止時間以前獲取鎖,若是截止時間到了任然沒法獲取到鎖,則返回。

Lock簡單使用與注意事項

其中Lock的使用方式也很簡單,具體代碼以下所示:post

Lock lock = ....;具體實現類
lock.lock();
try {
} finally {
lock.unlock();//建議在finally中釋放鎖
}
複製代碼

當鎖定和解鎖發生在不一樣的範圍時,必定要注意確保在持有鎖時執行的全部代碼都受到try-finally或try-catch的保護,以確保在必要時釋放鎖。不要將獲取鎖的過程寫在try塊中,由於若是在獲取鎖(自定義鎖的實現)時發生了異常,異常拋出的同時,也會致使鎖無端釋放(由於一旦發生異常,就會走finally語句,若是這個異常(多是用戶自定義異常,用戶能夠本身處理)須要線程1來處理,可是接着執行了lock.unlock()語句致使了鎖的釋放。那麼其餘線程就能夠操做共享資源。有可能破壞程序的執行結果)。ui

Lock相關實現類實現鎖機制

爲了使用Lock接口實現相關鎖功能時,會涉及如下類和接口,這裏仍是把上篇文章提到的UML圖展現出來:spa

lock.png

上圖中,線程

  1. 綠色部分爲:其中ReentrantLock(重入鎖)、WriteLock、ReadLock都是Lock的實現類。Segment爲ReentrantLock的子類(在後續文章,ConcurrentHashMap的講解中咱們會說起)。 ReentrantReadWriteLock (讀寫鎖)的實現使用了WriteLock與ReadLock類。
  2. 紫色部分爲:其中AbstractQueuedSynchronizerAbstractQueuedLongSynchronizer都爲AbstractOwnableSynchronizer的子類,該兩個類中都維護了一個同步隊列,用於線程的併發執行。在該兩個類中擁有名爲ConditionObject(爲Conditon的實現類)的內部類,只是其內部實現不一樣。在ConditionObject內部維護了一個等待隊列,用於控制線程的等待與喚醒。

基本代碼結構

在瞭解了Lock相關實現類實現鎖機制後,這裏給實現該鎖機制的大體代碼結構(根據不一樣需求,部分方法實現可能不同,這裏只是一個參考,並非樣本代碼)。具體代碼以下所示:

class LockImpl implements Lock {

    private final sync mSync = new sync();
    @Override
    public void lock() {
        mSync.acquire(1);
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
        mSync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return mSync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return mSync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        mSync.release(1);
    }

    @Override
    public Condition newCondition() {
        return mSync.newCondition();
    }
    
	 //這裏也能夠繼承AbstractQueuedLongSynchronizer
    private static class sync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean isHeldExclusively() {...}
        @Override
        protected boolean tryAcquire(int arg) {...}
        @Override
        protected boolean tryRelease(int arg) {...}
        @Override
        protected int tryAcquireShared(int arg) {...}
        @Override
        protected boolean tryReleaseShared(int arg) {...}
        final ConditionObject newCondition() {...}
    }
}
複製代碼

從代碼中咱們能夠看出,在整個Lock接口下實現的鎖機制中,AQS(這裏咱們將AbstractQueuedSynchronizer 或AbstractQueuedLongSynchronizer統稱爲AQS)是實現鎖的關鍵,整個鎖的實現是在Lock類的實現類中聚合AQS來實現的,從代碼層面上來講,Lock接口(及其實現類)是面向使用者的,它定義了使用者與鎖交互的接口(好比能夠容許兩個線程並行訪問),隱藏了實現細節。AQS與Condition纔是真正的實現者,它簡化了鎖的實現方式,屏蔽了同步狀態管理、線程的排隊、等待與喚醒等底層操做。

總結

  1. Lock接口(及其實現類)相比synchronized有以下優勢:
  • 鎖的釋放與獲取不在是隱式的,容許鎖在不一樣的做用範圍內獲取和釋放`,並容許以任何順序獲取和釋放多個鎖。
  • 能被中斷的獲取鎖,獲取到鎖的線程可以響應中斷,當獲取到鎖的線程被中斷時,中斷異常會被拋出,同時鎖也會被釋放
  • 超時獲取鎖:在指定的截止時間以前獲取鎖,若是截止時間到了任然沒法獲取到鎖,則返回。
  1. 在使用Lock的時候注意,必定要確保必要時釋放鎖
  2. 在整個Lock接口下實現的鎖機制中,AQS(上文進行了統稱)與Condition纔是真正的實現者。
相關文章
相關標籤/搜索