天天看點

Java并發技術總結之六——Java鎖分類六. Java 鎖分類

六. Java 鎖分類

《Java并發程式設計:Lock》

《java 鎖 Lock接口詳解》

《[死磕 java同步系列之ReentrantLock源碼解析(一)——公平鎖、非公平鎖]》)

6.1 Java 鎖的分類

鎖的類型目前感覺可以分成兩大類:synchronized 關鍵字,以及 Lock, ReadWriteLock 鎖以及 Reentrant 為字首修飾的實作類 (ReentrantLock, ReentrantReadWriteLock);

其他角度來看,按照不同分類類型的鎖:

  • 實作方式:synchronized / Lock, ReadWriteLock 及其實作類;
  • 可中斷性:synchronized / Lock
  • 公平性:ReentrantLock 構造函數中,傳入 boolean 值,可以控制公平性,預設非公平;公平鎖按照鎖的申請順序配置設定鎖,申請鎖時間最長的線程在下一次會最早得到鎖;非公平鎖不對申請鎖的時間進行保證,是以可能導緻某個線程永遠都擷取不到鎖;
  • 可重入性:synchronized / ReentrantLock,如果一個線程已經獲得了一個對象鎖,此後該線程再請求進入被該對象鎖的同步代碼塊時,由于該線程之前已經擷取了這個對象鎖,是以可以直接進入該鎖的同步代碼塊;
  • 讀寫性:ReadWriteLock;

可重入性的原理:參考《深入了解 Java 虛拟機》P391

可重入鎖和不可重入:參考《Java不可重入鎖和可重入鎖了解》

對于一個對象,進入對象鎖的代碼域之後,線程對該鎖進行計數。沒有進入鎖時,該鎖的計數值為 0。第一個擷取到該鎖的線程獲得該對象鎖,此後每多一個線程對該鎖進行申請,則計數值 +1。每當有一個線程釋放了這個鎖,則該鎖對應的計數值 -1。直到這個鎖的計數值重新回到 0,其他線程才可以擷取到該鎖的所有權。

我對于對象鎖的了解:

  • 對于 synchronized,可以為 synchronized 方法的執行個體對象 this,或者 synchronized(object) 的 object,都是對象鎖;
  • 對于 Lock,就是 lock 對象本身;

不可重入鎖:以可重入的對立面了解即可:對于某個 Lock 的實作,如果該 Lock 被線程A鎖住,線程A的其他對象想要擷取該 Lock,但由于在此之前 Lock 已經被鎖住,是以這裡不能擷取到該 Lock。總之感覺不可重入鎖并不是一種合适的 Lock 的類型。

不可重入鎖的代碼執行個體如下:

public class Lock {
    private boolean isLocked = false;
    public synchronized void lock() throws InterruptedException {
        while(isLocked) {    
            wait();
        }
        isLocked = true;
    }
    public synchronized void unlock() {
        isLocked = false;
        notify();
    }
}

public class Count {
    Lock lock = new Lock();
    public void print() {
        lock.lock();
        doAdd();
        lock.unlock();
    }
    public void doAdd() {
        lock.lock();
        //do something
        lock.unlock();
    }
}           

複制

Lock 接口主要有六個方法:

// 擷取鎖
void lock();
// 擷取鎖,可中斷
void lockInterruptibly() throws InterruptedException;
// 嘗試擷取鎖;如果沒有擷取到,則傳回 false;如果擷取到則傳回 true
boolean tryLock();
// 嘗試在某段時間内擷取鎖,如果等待一段時間仍沒有擷取到則傳回 false;
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 釋放鎖
void unlock();
// 條件鎖
Condition newCondition();           

複制

6.2 公平鎖與非公平鎖

為什麼ReentrantLock預設采用的是非公平模式?因為非公平模式效率比較高。非公平模式會在一開始就嘗試兩次擷取鎖,如果當時正好 state 的值為 0,它就會成功擷取到鎖,少了排隊導緻的阻塞/喚醒過程,并且減少了線程頻繁的切換帶來的性能損耗。

同時非公平模式也存在弊端。非公平模式有可能會導緻一開始排隊的線程一直擷取不到鎖,導緻線程餓死。

公平鎖與非公平鎖在源碼上的差別:

  • 公平鎖:在嘗試擷取鎖的時候,首先會判斷 AQS 線程隊列的頭部是否為目前線程(隊列的特性決定了公平性),如果目前線程位于 AQS 線程隊列的頭部,則說明目前線程等待的時間最長,目前線程有資格比較狀态,然後才能擷取到鎖。
  • 非公平鎖:嘗試擷取鎖的時候不會判斷 AQS 隊列頭部資訊,直接進行比較狀态并嘗試擷取鎖。