Lock體系

Lock簡介

鎖是用來控制多個線程訪問共享資源的方式,一般來說,一個鎖能夠防止多個線程同時訪問共享資源。在Lock接口出現之前,java程序主要是靠synchronized關鍵字實現鎖功能的,而JDK5之後,併發包中增加了lock接口,它提供了與synchronized一樣的鎖功能。雖然它失去了像synchronize關鍵字隱式加鎖解鎖的便捷性,但是卻擁有了鎖獲取和釋放的可操作性,可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具備的同步特性。通常使用顯示使用lock的形式如下:

Lock lock = new ReentrantLock();
try {
	lock.lock();
	// 以下代碼只有一個線程可以運行
	...
}
finally{
	lock.unlock();
}

需要注意的是synchronized同步塊執行完成或者遇到異常是鎖會自動釋放,而lock必須調用unlock()方法釋放鎖,因此在finally塊中釋放鎖

Lock常用API

lock體系擁有可中斷的獲取鎖以及超時獲取鎖以及共享鎖等內建鎖不具備的特性。

 void lock(); // 獲取鎖 
 void lockInterruptibly() throws InterruptedException; // 獲取鎖的過程能夠響應中斷 
 boolean tryLock(); // 非阻塞式響應中斷能立即返回,獲取鎖返回true反之爲false 
 boolean tryLock(long time,TimeUnit unit);// 超時獲取鎖,在超時內或未中斷的情況下能獲取鎖 
 Condition newCondition(); // 獲取與lock綁定的等待通知組件,當前線程必須先獲得了鎖才能等待,等待會釋放鎖,再次獲取到鎖才能從等待中返回。

基 本 上 所 有 的 方 法 的 實 現 實 際 上 都 是 調 用 了 其 靜 態 內 存 類 Sync 中 的 方 法 , 而 Sync 類 繼 承 了 AbstractQueuedSynchronizer(AQS--簡稱同步器)

AQS

  • 同步器是用來構建鎖以及其他同步組件的基礎框架,它的實現主要是依賴一個int狀態變量以及通過一個 FIFO隊列共同構成同步隊列。
  • 子類必須重寫AQS的protected修飾的用來改變同步狀態的方法,其他方法主要是實現了排隊與阻塞機制。int狀態的更新使用getState()setState()以及compareAndSetState()
  • 子類推薦使用靜態內部類來繼承AQS實現自己的同步語義。同步器即支持獨佔鎖,也支持共享鎖。

同步器是實現鎖(也可以是任意同步組件)的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的語義。可以這樣理解二者的關係:鎖是面向使用者,它定義了使用者與鎖交互的接口,隱藏了實現細節;同步器是面向鎖的實現者,它簡化了鎖的實現方式,屏蔽了同步狀態的管理,線程的排隊,等待和喚醒等底層操作。鎖和同步器很好的隔 離了使用者和實現者所需關注的領域。

AQS的模板方法設計模式

AQS的設計是使用模板方法設計模式,它將一些與狀態相關的核心方法開放給子類重寫,而後AQS會使用子類重寫的關於狀態的方法進行線程的排隊、阻塞以及喚醒等操作。

  1. 同步組件(這裏不僅僅指鎖,還包括CountDownLatch等)的實現依賴於同步器AQS,在同步組件實現中,使用 AQS的方式被推薦定義繼承AQS的靜態內存類;
  2. AQS採用模板方法進行設計,AQS的protected修飾的方法需要由繼承AQS的子類進行重寫實現,當調用AQS的子類的方法時就會調用被重寫的方法;
  3. AQS負責同步狀態的管理,線程的排隊,等待和喚醒這些底層操作,而Lock等同步組件主要專注於實現同步語 義;
  4. 在重寫AQS的方式時,使用AQS提供的getState(),setState(),compareAndSetState()方法進行修改同步狀態。


AQS提供的模板方法可以分爲3類:

  1. 獨佔式獲取與釋放同步狀態;
  2. 共享式獲取與釋放同步狀態;
  3. 查詢同步隊列中等待線程情況;

自己實現一個簡易Lock鎖

package www.bit.java;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

class Metux implements Lock{
    private Syns syns = new Syns();
    static class Syns extends AbstractQueuedSynchronizer{
        @Override
        protected boolean tryAcquire(int arg) {
            if(arg != 1){
                throw new RuntimeException("arg參數不爲1");
            }
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            if(getState() == 1){
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        /*
        當前同步器是否在獨佔模式下被線程佔用
        表示是否被當前線程所獨佔
         */
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        Condition newCondition(){
            return new ConditionObject();
        }
    }
    @Override
    public void lock() {
        syns.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        syns.acquireInterruptibly(1);
    }

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

    /*
    超時獲取鎖,在超時內或未中斷的情況下能獲取鎖
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

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

    /*
    獲取與lock綁定的等待通知組件,
    當前線程必須先獲得了鎖才能等待,等待會釋放鎖,再次獲取到鎖才能從等待中返回。
     */
    @Override
    public Condition newCondition() {
        return syns.newCondition();
    }
}
public class TestDemo {
    private static Metux metux = new Metux();
    public static void main(String[] args) {
        for(int i = 0;i < 10;i++){
            Thread thread = new Thread(() -> {
                metux.lock();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    metux.unlock();
                }
            });
            thread.start();
        }
    }
}


上面這個例子實現了獨佔鎖的語義,在同一時刻只允許一個線程佔有鎖。我們在主線程中啓動了10個線程,分別睡眠2s。從執行情況我們可以看出,當前Thread-1正在執行並且佔有鎖而其他線程需要等待獲取鎖。

按照推薦的方式,Mutex定義了一個繼承AQS的靜態內部類Sync,並且重寫了AQS的tryAcquire等等方法,而對state的更新也是利用了setState(),getState(),compareAndSetState()這三個方法。在實現lock接口中的方法也只是調用了 AQS提供的模板方法(因爲Sync繼承AQS)。

從這個例子就可以很清楚的看出來,在同步組件的實現上主要是利用了AQS,而AQS“屏蔽”了同步狀態的修改,線程排隊等底層實現,通過AQS的模板方法可以很方便的給同步組件的實現者進行調用。而針對用戶來說,只需要調用同步組件提供的方法來實現併發編程即可。

同時在新建一個同步組件時需要把握的兩個關鍵點是:

  1. 實現同步組件時推薦定義繼承AQS的靜態內存類,並重寫需要的protected修飾的方法;
  2. 同步組件語義的實現依賴於AQS的模板方法,而AQS模板方法又依賴於被AQS的子類所重寫的方法。

總的來說,同步組件通過重寫AQS的方法實現自己想要表達的同步語義,而AQS只需要同步組件表達的true和false 即可,AQS會針對true和false不同的情況做不同的處理。

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