Java併發基礎 - ReentrantLock

主要內容:

  • ReentrantLock Demo示例
  • 公平鎖和非公平鎖的詳細實現
  • 公平和非公平的定義
  • ReentrantLock使用場景
  • 和synchronized的簡單比較

一、 ReentrantLock

1. 先看Demo示例,再細細道來原理:

@Slf4j
public class LockDemo {
    //ReentrantLock無參構造方法,sync = new NonfairSync();默認非公平鎖
    private Lock lock = new ReentrantLock();

    private void workOn() {
        log.info(Thread.currentThread().getName() + ":上班!");
    }

    private void workOff() {
        log.info(Thread.currentThread().getName() + ":下班");
    }

    private void work() {
        try {
            //加鎖
            lock.lock();
            workOn();
            log.info(Thread.currentThread().getName() + "工作中...");
            Thread.sleep(100);
            workOff();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //釋放鎖
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        LockDemo lockDemo = new LockDemo();

        int i = 0;
        List<Thread> list = new ArrayList<>(30);
        do {
            Thread a = new Thread(lockDemo::work, "小A_" + i);
            Thread b = new Thread(lockDemo::work, "小B_" + i);
            list.add(a);
            list.add(b);
        } while (i++ < 10);
        list.parallelStream().forEach(Thread::start);
    }
}

執行之後,可以看到交替輸出:

小B_6:上班!
小B_6工作中...
小B_6:下班
小B_7:上班!
小B_7工作中...
小B_7:下班
...省略...
小B_3:上班!
小B_3工作中...
小B_3:下班

即成功的加上了鎖。

2. ReentrantLock類的定義

public class ReentrantLock implements Lock, java.io.Serializable {...}

主要是Lock接口,其定義如下:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

3. ReentrantLock中的三個內部類

//Sync抽象類繼承了AQS,使用AQS的state來展示持有鎖的數目
abstract static class Sync extends AbstractQueuedSynchronizer{}

//非公平鎖
static final class NonfairSync extends Sync{}

//公平鎖
static final class FairSync extends Sync {}

4. NonfairSync類的實現

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

    //加鎖
    final void lock() {
        ////通過CAS來獲取同步狀態 也就是鎖
        if (compareAndSetState(0, 1))
            //當前線程佔有鎖
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //獲取失敗 進入AQS同步隊列排隊等待 執行AQS的acquire方法
            acquire(1);
    }
    //獲取鎖
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

acquire方法:

//AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
    //tryAcquire方法實際上是NonfairSync類的tryAcquire方法
    //如果獲取不到鎖,
    if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//ReentrantLock.java
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}

非公平鎖的獲取:

final boolean nonfairTryAcquire(int acquires) {
    //當前線程
    final Thread current = Thread.currentThread();
    int c = getState();
    //AQS的state=0,代表着鎖未被搶佔
    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;
    }
    //鎖被其他線程搶佔了,返回false,未獲取到鎖
    return false;
}

非公平鎖中,搶到AQS的同步狀態的未必是同步隊列的首節點,只要線程通過CAS搶到了同步狀態或者在acquire中搶到同步狀態,就優先佔有鎖,而相對同步隊列這個嚴格的FIFO隊列來說,所以會被認爲是非公平鎖。

5. FairSync類的實現

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        //嚴格按照AQS的同步隊列要求去獲取同步狀態。加鎖的時候就調用了下面的tryAcquire方法
        acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //鎖未被搶佔
        if (c == 0) {
            //沒有前驅節點,並且CAS獲取到了同步狀態,則獨佔
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //鎖已被搶佔且線程重入
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

公平鎖的實現直接調用AQS的acquire方法,acquire中調用tryAcquire。和非公平鎖相比,這裏不會執行一次CAS,接下來在tryAcquire去搶佔鎖的時候,也會先調用hasQueuedPredecessors看看前面是否有節點已經在等待獲取鎖了,如果存在則同步隊列的前驅節點優先。

//隊列中是否有前置線程。當前線程隊列頭,或者隊列爲空,返回false
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

二、 應用方式

1. 普通方式

//默認Nonfair,傳入true使用Fair
Lock lock = new ReentrantLock();
try {
        lock.lock();
     //……
 }finally {
     lock.unlock();
 }

2. 帶返回結果的鎖

  • tryLock:嘗試獲取非公平鎖,直接返回獲取結果
public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
  • tryLock(long timeout, TimeUnit unit):帶超時時間的
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
  • acquireInterruptibly(int arg):如果該線程未被中斷則獲取鎖

三、和synchronized的比較

ReentrantLock synchronized
顯式的使用在同步方法或者同步代碼塊中 顯式的指定起始和結束位置
託管給JVM執行,不會因爲異常、或者未釋放而發生死鎖 手動釋放鎖

都是互斥同步(悲觀鎖)也叫做阻塞同步鎖,特徵是會對沒有獲取鎖的線程進行阻塞。

性能不是選擇他們的原因,如果不是synchronized無法實現的功能,如輪詢鎖、超時鎖和中斷鎖等,推薦首先使用synchronized,而針對鎖的高級功能,再使用ReentrantLock。

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