Lock(四) — 可重入锁(ReentrantLock)源码解析

一、概述

重入锁: 当该锁被线程A持有时,线程A仍可以重新获取该锁。
公平锁: 在获取锁时,如果线程A先对锁进行获取,则当锁释放后,线程A优先获得锁。
非公平锁: 在获取锁时,如果线程A先对锁进行获取,则当锁释放后,线程A不一定先得到锁。

通常情况下,非公平锁效率比公平锁效率高。

  1. 在获取公平锁时,如果同步队列内已经有等待的线程时,那么会优先给同步队列内的线程,而此时请求获取锁的线程就进入同步队列等待,存在线程切换开销。
  2. 在获取非公平锁时,会优先给当前请求获取锁的线程机会,如果获取锁成功就直接返回(降低了线程切换的开销),否则将当前获取锁的线程加入同步队列进行等待。

二、ReentrantLock 类图

ReentrantLock 类图

上图是 ReentrantLock 类图,从图中可知:

  1. ReentrantLock 实现了 Lock 接口,因此具备了锁的相关操作。
  2. ReentrantLock 内部包含一个继承了 AbstractQueuedSynchronizerSync 同步器,因此所有用户态的 Lock 操作都委托给 Sync 来执行。
  3. Sync 有两个子类:FairSyncNonfairSync,他们实现了锁获取策略的公平性问题。
  4. 在 ReentrantLock 中使用到了 AQS ,可以参考:
    1. Lock(二) — 队列同步器(AQS)浅析
    2. Lock(三) — 自定义AQS (实践)

三、源码解析

1. ReentrantLock 的使用

获取锁和释放锁的操作使用 before...after 模式,且获取锁的操作不放在 try 中。

ReentrantLock lock = new ReentrantLock();
lock.lock(); // before
try {
	// 执行逻辑操作
} finally {
	lock.unlock(); //after
}

2 ReentrantLock 的构造

默认使用的是非公平锁的策略

// 默认的 ReentrantLock 使用的是非公平锁的策略
public ReentrantLock() {
    sync = new NonfairSync();
}

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

3. ReentrantLock 锁的获取

在获取锁时,会调用到 AQS 的 acquire() 方法。该方法的具体解析请参考文章: Lock(二) — 队列同步器(AQS)浅析

3.1 非公平锁的获取

// ReentrantLock.class
public void lock() {
    sync.lock(); // sync分公平和非公平锁两种
}

// ReentrantLock.NonfairSync.class
final void lock() {
	// 非公平锁:让当前获取锁的线程优先尝试获取锁操作(这里第1次)
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
    	// 执行到这里说明上面获取锁没有成功
        acquire(1); //acquire(1)方法是AQS内部的
}

// AbstractQueuedSynchronizer.class
public final void acquire(int arg) {
	/* 
	 * 分2步进行判断:
	 * 1.tryAcquire(arg):尝试获取锁(获取锁成功,则直接返回)
	 * 2.addWaiter(Node.EXCLUSIVE):当tryAcquire(arg)获取锁失败后,则使当前线程进入同步队列等待。
	 * 3.acquireQueued(node, arg):使当前获取锁的线程进入自旋,循环判断是否可以获取到锁,并返回是否中断该线程的标记。
	 * 说明:acquireQueued()方法内部不响应线程的中断操作,所以将是否中断操作抛出,在这里执行。
	 */
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        //如果返回中断,则调用当前线程的interrupt()方法
        selfInterrupt();
}

// ReentrantLock.NonfairSync.class
protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires); //非公平锁的获取
}

// ReentrantLock.Sync.class
// 主要分为下面3个步骤
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState(); //1.获取锁是否已经被其他线程持有(0表示该锁没有被线程持有)。
    if (c == 0) { //2.当c==0时,表示该锁没有线程持有
    	/*
    	 * 2.1 这里存在多个线程同时操作的问题,所以得通过CAS原子操作更新state状态。
    	 * 一旦state更新成功,则其他线程在执行到c==0时或通过CAS再次更新state状态时,就会返回false。
    	 */
        if (compareAndSetState(0, acquires)) {  // (非公平锁:这里第2次尝试让当前线程获取锁)
        	// 2.2 将当前持有锁的线程保存到AQS的变量中。
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    /*
     * 3.执行到这里,表明锁被线程持有(该线程可能是持有锁的线程,也可能是其他线程)
     * 这里判断当前的线程跟持有锁的线程是否是相同,如果相同,就再次获取锁成功(锁的可重入性);
     */
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires; // 3.1 这里增加state表示重入次数。
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc); //3.2 这里由于当前线程跟持有锁的线程相同,所以不存在竞争,因此不需要通过CAS操作更新state状态。
        return true;
    }
    return false;
}

3.2 公平锁的获取

// ReentrantLock.class
public void lock() {
    sync.lock(); // sync分公平和非公平锁两种
}

// ReentrantLock.FairSync.class
final void lock() {
	acquire(1); //acquire(1)方法是AQS内部的
}

// AbstractQueuedSynchronizer.class
public final void acquire(int arg) {
	// 该方法在非公平锁获取时已经阐述,此处不解释。
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        selfInterrupt();
}

// ReentrantLock.FairSync.class
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState(); //1.获取锁是否已经被其他线程持有(0表示该锁没有被线程持有)。
    if (c == 0) {//2.当c==0时,表示该锁没有线程持有。
    	/*
    	 * 2.1 hasQueuedPredecessors():表示判断同步队列中是否有其他线程在等待获取锁(true=同步队列有其他线程在等待)。
    	 * 说明:这个方法可以保证获取锁的公平性,先到先得,后来的线程在同步队列等待(队列特点:FIFO)。
    	 */
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) { //2.2 通过CAS操作更新state状态。
            setExclusiveOwnerThread(current); //2.3 将当前持有锁的线程保存到AQS的变量中。
            return true;
        }
    }
    /*
     * 3.执行到这里,表明锁被线程持有(该线程可能是持有锁的线程,也可能是其他线程)
     * 这里判断当前的线程跟持有锁的线程是否是相同,如果相同,就再次获取锁成功(锁的可重入性);
     */
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc); //更新状态 state表示线程重入次数。
        return true;
    }
    return false;
}

4. ReentrantLock 锁的释放

// ReentrantLock.class
public void unlock() {
    sync.release(1); 
}

// AbstractQueuedSynchronizer.class
public final boolean release(int arg) {
    if (tryRelease(arg)) { //尝试释放锁
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h); // 此处尝试释放节点
        return true;
    }
    return false;
}

// ReentrantLock.Sync.class
protected final boolean tryRelease(int releases) {
    int c = getState() - releases; //1.这里每释放一次,state就要减小releases数量(可重入性)
    // 2.这里判断释放锁的线程是否是当前线程
    if (Thread.currentThread() != getExclusiveOwnerThread()) 
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) { //3.当state==0,说明该锁没有被任何线程持有
        free = true;
        setExclusiveOwnerThread(null); //4.将独占锁关联的线程信息移除
    }
    setState(c); //因为第2步已经判断是否是同一个线程,所以这里的操作是线程安全的。
    return free;
}

四、小结

1. 如何实现公平锁?

在获取锁的时候,通过 hasQueuedPredecessors() 方法判断同步队列内 是否存在 其他线程也在等待获取锁资源。

  1. 若存在: 这说明有比当前线程早的,为了公平性,则需要让他们先获得锁,而将自己添加进入同步队列进行等待。
  2. 若不存在: 说明当前线程可以直接获取锁资源,当前线程是最先获取锁的。

2. 如何实现非公平锁?

非公平锁提供了两次优先于同步队列获取锁的机会。

第一次: 在调用 lock() 方法时,先尝试让当前线程直接通过 compareAndSetState(0, 1) (CAS)操作来更新 state 状态,更新成功则说明当前线程获取到锁。
第二次: 在第一次没有获取到锁时,在 tryAcquire() 方法中,再次尝试让当前线程直接通过 compareAndSetState(0, 1) (CAS)操作来更新 state 状态,更新成功则说明当前线程获取到锁。

两次都没有成,则将当前线程放入同步队列等待。

注意:

  1. 不管是共配锁,还是非公平锁,同步队列获取锁都是有先后顺序的。
  2. 非公平锁只是给 当前线程 提供了2次优先竞争锁的机会。

五、参考

  1. ReentrantLock(重入锁)功能详解和应用演示
  2. ReentrantLock实现原理深入探究
  3. 一张图读懂非公平锁与公平锁
发布了158 篇原创文章 · 获赞 26 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/Love667767/article/details/104874746