深刻Java Lock鎖

上篇文章已經對多線程有個初步的認識了,此次咱們來看看Java的Lock鎖,主要有如下知識點:java

  • AQS
  • ReentrantLock
  • ReentrantReadWriteLock
  • Lock和synchronized的選擇

在學習Lock鎖以前,咱們先來看看什麼是AQS?算法

AQS

  • AQS其實就是一個能夠給咱們實現鎖的框架,juc包中不少可阻塞的類好比ReentrantLock、 ReadWriteLock都是基於AQS構建的。
  • 內部實現的關鍵是:先進先出的隊列、state狀態
  • 在AQS中實現了對等待隊列的默認實現,子類只要重寫部分的代碼便可實現(大量用到了模板代碼)
  • AQS同時提供了互斥模式(exclusive)和共享模式(shared)兩種不一樣的同步邏輯。通常狀況下,子類只須要根據需求實現其中一種模式,固然也有同時實現兩種模式的同步類,如ReadWriteLock
注意:ReentrantLock不是AQS的子類,其內部類Sync纔是AQS的子類。

image

State狀態

AQS維護了一個volatile int類型的state變量,用來表示當前同步狀態。
volatile雖然不能保證操做的原子性,可是保證了當前變量state的可見性。多線程

compareAndSetState

compareAndSetState用來修改state狀態,它是一個原子操做,底層實際上是調用系統的CAS算法,有關CAS可移步:CAS框架

protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  }

請求資源

acquire

acquire(int arg) 以獨佔方式獲取資源,若是獲取到資源,線程直接返回,不然進入等待隊列,直到獲取到資源爲止,且整個過程忽略中斷的影響。函數

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

若是tryAcquire(int)方法返回true,則acquire直接返回,不然當前線程須要進入隊列進行排隊。
addWaiter()將該線程加入等待隊列的尾部,並標記爲獨佔模式;學習

ReentrantLock

學習ReentrantLock以前先來看看它實現的Lock接口ui

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
  • lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來獲取鎖的。
  • unLock()方法是用來釋放鎖的。
  • newCondition()方法是建立一個條件對象,用來管理那些獲得鎖可是不能作有用工做的線程。

ReentrantLock,意思是"可重入鎖",線程能夠重複地得到已經持有的鎖。 ReentrantLock是惟一實現了Lock接口的類。接下來咱們來看看有關源碼:this

AQS子類

ReentrantLock實現了三個內部類,分別是Sync、NonfairSync和FairSyncspa

abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync

這些內部類都是AQS的子類,這就印證了咱們以前所說的:AQS是ReentrantLock的基礎,AQS是構建鎖的框架.線程

構造器

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

默認實現的是非公平鎖,傳入true表示使用公平鎖。

加鎖

  • ReentrantLock中加鎖使用的是lock方法
  • 默認使用非公平鎖的lock方法

加鎖流程

首先會經過CAS方法,嘗試將當前的AQS中的State字段改爲從0改爲1,若是修改爲功的話,說明原來的狀態是0,並無線程佔用鎖,並且成功的獲取了鎖,只須要調用setExclusiveOwnerThread函將當前線程設置成持有鎖的線程便可。不然,CAS操做失敗以後,和普通鎖同樣,調用父類AQS的acquire(1)函數嘗試獲取鎖。

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

     
        final void lock() {
            if (compareAndSetState(0, 1))//嘗試獲取鎖
                setExclusiveOwnerThread(Thread.currentThread());
            else  //獲取失敗則調用AQS的acquire方法
                acquire(1);
        }

而在AQS的acquire(1)函數中,會判斷tryAcquire(1)以及acquireQueued(addWaiter(Node.EXCLUSIVE), arg),若是嘗試獲取失敗而且添加隊列成功的話,那麼就會調用selfInterrupt函數中斷線程執行,說明已經加入到了AQS的隊列中。

注意:AQS的tryAcquire(1)是由子類Sync(也就是ReentrantLockd的靜態內部類)本身實現的,也就是用到了模板方法,接下來咱們去看看子類的實現。

tryAcquire是在NonfairSync類中實現的,其中調用了nonfairTryAcquire函數。

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//獲取當前線程狀態
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//可重入鎖
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

nonfairTryAcquire函數中,會嘗試讓當前線程去獲取鎖:

  1. 獲取當前線程,以及AQS的狀態
  2. 若是當前AQS的狀態爲0的話,那麼說明當前的鎖沒有被任何線程獲取,則嘗試作一次CAS操做,將當前的狀態設置成acquires,若是設置成功了的話,那麼則將當前線程設置成鎖持有的線程,而且返回true,表示獲取成功。
  3. 若是當前的狀態不爲0的話,說明已經有線程持有鎖,則判斷當前線程與持有鎖的線程是否相同,若是相同的話,則將當前的狀態加上acquires從新將狀態設置,而且返回true,這也就是重入鎖的緣由。
  4. 若是當前線程沒有獲取到鎖的話,那麼就會返回false,表示獲取鎖失敗。

源碼參考:ReentrantLock中的NonfairSync加鎖流程

ReentrantReadWriteLock

概述

咱們知道synchronized內置鎖和ReentrantLock都是互斥鎖(一次只能有一個線程進入到臨界區(被鎖定的區域))

而ReentrantReadWriteLock是一個讀寫鎖

  • 在讀取數據的時候,能夠多個線程同時進入到到臨界區(被鎖定的區域)
  • 在寫數據的時候,不管是讀線程仍是寫線程都是互斥

通常來講:咱們大多數都是讀取數據得多,修改數據得少。因此這個讀寫鎖在這種場景下就頗有用了!

ReentrantReadWriteLock實現了ReadWriteLock接口.
接口只有兩個方法,一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操做分開,分紅2個鎖來分配給線程,從而使得多個線程能夠同時進行讀操做

public interface ReadWriteLock {  
    Lock readLock();
    Lock writeLock();
}

性質

  • 讀鎖不支持條件對象,寫鎖支持條件對象
  • 讀鎖不能升級爲寫鎖,寫鎖能夠降級爲讀鎖
  • 讀寫鎖也有公平和非公平模式
  • 讀鎖支持多個讀線程進入臨界區,寫鎖是互斥的

和ReentrantLock相比,ReentrantReadWriteLock多了ReadLockWriteLock兩個內部類。

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {       
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;

讀鎖和寫鎖的狀態表示

abstract static class Sync extends AbstractQueuedSynchronizer {

        static final int SHARED_SHIFT   = 16;// 高16位爲讀鎖,低16位爲寫鎖
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
      
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

讀寫鎖對於同步狀態的實現是將變量切割成兩部分,高16位表示讀,低16位表示寫。

看個實際例子

class czy{    
     private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     ........
     public void read(Thread thread) {
        rwl.readLock().lock();
        try {
            long start = System.currentTimeMillis();
             
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在進行讀操做");
            }
            System.out.println(thread.getName()+"讀操做完畢");
        } finally {
            rwl.readLock().unlock();
        }
    }
}

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鎖的知識點就到這裏,若是想了解更多請參考下面連接。

參考
Java3y多線程
Java技術之AQS詳解

相關文章
相關標籤/搜索