同步器 ReentrantLock & ReetrantReadWriteLock

前言

  前面已经介绍了 AQS(抽象队列同步器)的实现 https://www.cnblogs.com/hysxm/p/14702030.html,本片文章主要对常用的几种同步器做介绍,并对比各自的特点,熟悉其使用场景,主要包含:

  • ReentrantLock :可重入独占锁

一、ReentrantLock 

  ReentrantLock是可重入的独占锁,同时只有一个线程可以获取该锁,其他获取该锁的线程会进入 AQS 的阻塞队列

   从 ReentrantLock  的UML类图可以看出,其最终还是通过实现 AQS 实现的,不熟悉AQS的可以先学习下,会对ReentrantLock 的理解有很大的帮助:https://www.cnblogs.com/hysxm/p/14702030.html

  ReentrantLock  可以通过构造函数选择公平锁 (FairSync) 与非公平锁 (NonfairSync),两者都是继承自 Sync,而Sync 是实现AbstractQueuedSynchronizer 的抽象类。

  ReetrantLock 中 AQS的 state 值代表锁的可重入次数,state=0代表没有任何线程持有;当线程第一次获取资源时,会将state设置为1;如果在没有释放的情况下,持有资源的线程再次进入锁,state+=1,此时state=2 代表锁的可重入次数。释放锁时,使用CAS对state减一,当state=0时,表明当前线程释放锁。

  下面分别从 lock() 和 unLock() 分析 ReetrantLock 获取和释放锁的实现

1.1、lock() 获取锁

  NonfairSync 非公平锁获取方式

final void lock() {
    if (compareAndSetState(0, 1)) // CAS设置state=1,成功代表获取到锁,并将锁的拥有者设置为当前线程
        setExclusiveOwnerThread(Thread.currentThread());
    else // 失败,调用AQS的独占锁获取锁的方式
        acquire(1);
}

// AQS的独占锁资源获取方法,AQS一文中已经详细解读,不再赘述
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

// 最终调用了Sync中 实现的非公平资源获取方法
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) { // 资源空闲可以尝试获取
        if (compareAndSetState(0, acquires)) {  // 尝试使用CAS设置state值为1
            setExclusiveOwnerThread(current); // 获取锁成功
            return true;  // 获取资源成功
        }
    }
    // state != 0,当前线程是资源的占有者 => 重入锁,并设置state += 1
    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;
}

 FairSync 公平锁获取方式

// 加锁
final void lock() {
    acquire(1); // 调用 AQS的独占锁资源获取方法
}

// AQS的独占锁资源获取方法,AQS一文中已经详细解读,不再赘述
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState(); // 获取当前资源值
    if (c == 0) { // 资源空闲可以尝试获取
        // 是否有其他线程早于当前线程 请求资源
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) { // CAS 设置 state值为 1,标识占有资源
            setExclusiveOwnerThread(current); // 设置独占锁资源拥有者为当前线程
            return true; // 获取资源成功
        }
    }
     // state != 0,当前线程是资源的占有者 => 重入锁,并设置state += 1
    else if (current == getExclusiveOwnerThread()) { 
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

  注意:lock() 、nonfairTryAcquire() 方法是final修饰的,不可以继承修改。原因在于:ReetrantLock的重入锁机制,state的值代表了可重入次数,如果可以修改,就会破坏ReetrantLock 的实现。

  AbstractOwnableSynchronizer 中的 exclusiveOwnerThread 属性,作为排它锁拥有者标记,通过该方式判断是否是当前线程持有锁,进而实现可重入的功能。

  ReetrantLock公平锁获取锁的方式和非公平的基本一致,只是公平锁在CAS设置state值之前先校验了是否有在排队的前驱结点,而非公平锁是上来直接取尝试获取锁。

1.2、unlock()释放锁

public void unlock() {
    sync.release(1); // 释放资源 1
}

// AQS 独占模式释放资源方法
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 释放资源的实现
protected final boolean tryRelease(int releases) {
    int c = getState() - releases; // 当前state - releases(1)
    if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程没有持有锁,异常
        throw new IllegalMonitorStateException();
    boolean free = false;  // 是否完全释放资源,释放锁
    if (c == 0) { // 所有重入锁释放
        free = true;
        setExclusiveOwnerThread(null);  // 重置锁的占有者
    }
    setState(c);  // 重新设置state的值
    return free;
}

  ReentrantLock 释放锁,就是对state值进行减一操作,如果state=0,说明所有的重入锁已经释放,锁不再被当前线程持有。

1.3、小结

  ReentrantLock 内部通过继承 AQS 实现了公平锁和非公平锁,ReentrantLock 自定义同步器只是简单的实现了 AQS定义的获取和释放资源的(操作state)抽象方法,对于多线程的竞争&排队等全部是AQS实现了,由此可见AQS功能的强大。因为其限定了获取和释放锁的时候,state的值加一减一,所以 ReentrantLock 中state的含义是可重入的次数。

 二、ReetrantReadWriteLock

  ReentrantReadWriteLock 和 ReentrantLock一样都是可重入锁,其内部也实现了公平锁和不公平锁;不同的是,ReentrantReadWriteLock 按照操作的类型不同分为了读锁和写锁。

  源码结构如下

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    /** 读锁 */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** 写锁 */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** 内部同步类,实现AQS */
    final Sync sync;
    /** 默认构造方法,非公平 */
    public ReentrantReadWriteLock() {
        this(false);
    }
    /** 公平锁构造方法 */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
    /** 内部同步类,实现AQS */
    abstract static class Sync extends AbstractQueuedSynchronizer {}
    /** 非公平同步类 */
    static final class NonfairSync extends Sync{}
    /** 公平同步类 */
    static final class FairSync extends Sync{}
    /** 读锁 */
    public static class ReadLock implements Lock, java.io.Serializable{}
    /** 写锁 */
    public static class WriteLock implements Lock, java.io.Serializable{}
}

  从上面的UML类图和源码结构可以清楚的了解 ReetrantReadWriteLock 的层次,下面按照使用的方式从源码分析其实现原理。

  分析读写锁的源码之前,先来看下ReetrantReadWriteLock 中 Sync 的结构,该类和 ReentrantLock 的Sync 不同。

2.1、Sync

1、成员属性

// 共享的位移量  高16位代表读锁,低16位代表写锁
static final int SHARED_SHIFT   = 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;
// 当前线程读锁数量计数器
private transient ThreadLocalHoldCounter readHolds;
// 上一个成功获取读锁的线程的锁计数器
private transient HoldCounter cachedHoldCounter;
// 第一个获取读锁的线程
private transient Thread firstReader = null;
// 第一个读锁线程的计数
private transient int firstReaderHoldCount;

2、内部类:ThreadLocalHoldCounter & HoldCounter

static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

static final class HoldCounter {
    int count = 0;
    // 使用id,而不是引用,是为了防止垃圾存留
    final long tid = getThreadId(Thread.currentThread());
}

3、构造函数

Sync() {
    // 初始化本地线程计数器
    readHolds = new ThreadLocalHoldCounter();
    setState(getState()); // ensures visibility of readHolds
}

2.2、ReadLock

  2.2.1、首先看下读锁获取资源的实现 readLock.lock()

public void lock() {
    sync.acquireShared(1);
}

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    // 存在独占锁 并且不是当前线程拥有锁,则失败
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    // 读锁不需要阻塞,读锁的计数小于最大值,并且CAS设置state成功
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) { // 锁计数为0,代表是第一个读,设置firstReader为当前线程
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) { // 锁计数不为0,并且当前线程是第一个读线程
            firstReaderHoldCount++;
        } else { // 不是第一个读线程
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    // 如果上面的步骤失败,进入下面的方法
    return fullTryAcquireShared(current);
}

  读锁的资源获取首先要校验读锁是否需要阻塞,分要分为两种强情况:1、非公平锁:校验第一个入队的线程不是排他的  2、公平锁:没有正在等待获取资源的前置线程

  然后还会校验读锁的计数小于最大值,以及CAS设置state值成功。如果上述条件不成立,就会进入 fullTryAcquireShared 函数:

// 完整版本的读锁获取资源,主要为了处理 CAS 错误和 tryAcquireShared 中没有处理的重入读
final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) { // 自旋
        int c = getState();
        if (exclusiveCount(c) != 0) { // 有独占锁
            if (getExclusiveOwnerThread() != current) // 独占锁不是当前线程,获取资源失败
                return -1;
        } else if (readerShouldBlock()) { // 读锁需要阻塞
            // 获取读锁的第一个线程和当前线程相同,继续向下执行
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else { //
                if (rh == null) { // 前一个获取读锁的线程为空
                    rh = cachedHoldCounter; // 赋值上一个获取读锁的holdCounter
                    // 如果 holdCounter为空或者holdCounter的线程不是当前线程,则holdCounter赋值为当前线程
                    if (rh == null || rh.tid != getThreadId(current)) { 
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT) // 读锁计数达到最大值,抛出异常
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) { // cas 设置state值
            if (sharedCount(c) == 0) { // 没有读锁,则初始化
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) { // 当前线程是第一个获取读锁的线程,则firstReaderHolderCount + 1
                firstReaderHoldCount++;
            } else { //
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

  2.2.2、读锁释放资源 readLock.unlock()

// ReentrantReadWriteLock.ReadLock
public void unlock() {
    sync.releaseShared(1);
}

// AQS
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

// ReentrantReadWriteLock.Sync
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) { // 第一个获取读锁的线程是当前线程
        if (firstReaderHoldCount == 1) // 如果计数为1,重置 firstReader
            firstReader = null;
        else // 如果计数大于1,读锁计数减一
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) { // 自旋直到 CAS 设置 state 读锁标识减读锁单元成功
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

   读锁释放资源也是通过 自旋 + CAS state 的方式实现的,采用的是 Sync 中默认的实现。

 2.3、WriteLock

  2.3.1、获取锁 writeLock.lock()

  省略之前的调用AQS的代码,直接看下 tryAcquire(int) 的实现,WriteLock使用的是 ReentrantReadWriteLock.Sync.tryAcquire(int) 方法

/*
 * 流程:
 * 1. 如果读数量不为0或者写数量不为0,则失败
 * 2. 如果写的计数达到了最大值,则失败
 * 3. 否则,如果当前线程是重入请求或者满足排队策略,则表明锁是合适的
 */
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c); // 排它锁计数
    if (c != 0) {
        // 有读锁或者当前线程不是拥有独占锁线程
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT) // 独占锁计数达到最大值,抛出异常
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire 重入锁获取资源
        setState(c + acquires);
        return true;
    }
    // 写锁是否需要阻塞,FairSync需要判断有没有前驱结点等待  NonfairSync 不需要等待
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current); // 设置独占锁线程为当前线程
    return true;
}

  可以看到 writerShouldBlock() 在公平锁和非公平锁中有不同的实现。

  2.3.2、释放锁 WriteLock.unlock()

  同样的跳过 AQS 中的代码,直接看 ReentrantReadWriteLock 中 tryRelease() 的实现

protected final boolean tryRelease(int releases) {
    // isHeldExclusively 判断当前线程是否是拥有独占锁的线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free) // 所有的排它锁都已经释放
        setExclusiveOwnerThread(null);
    setState(nextc); // CAS 设置 state
    return free;
}

2.4、小结

  ReentrantReadWriteLock 中的读锁和写锁实现也是依赖于AQS框架的,读锁是共享锁,写锁是排它锁。读锁获取的时候,如果有写锁则获取失败;写锁获取的时候,如果有读锁或者其他线程拥有了写锁,则失败。

  

  

posted @ 2021-04-29 23:02  跬步-千里  阅读(102)  评论(0编辑  收藏  举报