09.JUC 锁 - ReentrantReadWriteLock

基本概念

ReadWriteLock (读写锁)内部维护着两个锁,一个用于写操作,即写锁;一个用于读操作,即读锁。

  • 写锁,是独占锁,即只能被一个线程持有。
  • 读锁,是共享锁,即可以同时被多个线程持有。

它的接口定义如下:

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

ReentrantReadWriteLock(可重入的读写锁) 继承自 ReadWriteLock ,它具有以下特性:

  • 重入:此锁允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入reader 使用读取锁。writer 可以获取读取锁,但 reader 不能获取写锁。

  • 锁策略:内部的同步器 Sync 实现了公平与非公平同步策略。

  • 锁降级:允许从写锁降级为读锁,实现方式是:先获取写锁,然后获取读锁,最后释放写锁。但是,从读锁升级到写锁是不可能的。


内部构造

1.构造函数

// 构造函数
public ReentrantReadWriteLock() {
    this(false);
}
public ReentrantReadWriteLock(boolean fair) {
    sync = ( fair ) ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

// 获取读锁
public ReentrantReadWriteLock.WriteLock writeLock() {
    return writerLock;
}

// 获取写锁
public ReentrantReadWriteLock.ReadLock readLock() {
    return readerLock;
}

构造函数中涉及到的几个类都在它内部定义:

// 同步器
static abstract class Sync extends AbstractQueuedSynchronizer {...}

// 非公平同步策略
final static class NonfairSync extends Sync {...}

// 公平同步策略
final static class FairSync extends Sync {...}

// 读锁
public static class ReadLock implements Lock, java.io.Serializable {...}

// 读锁
public static class WriteLock implements Lock, java.io.Serializable {...}

2.锁的重入计数

在 ReentrantReadWriteLock 中由于同时存在写锁、读锁,所以关于它们各自的重入计数是个问题。在这里并没有采用 2 个参数来表示,而是采用一个 32 位的二进制来同时表示各自的重入计数。具体原理如下:

// 这里用[高16位]表示[读锁]计数,[低16位]表示[写锁]计数。
00000000 00000000 00000000 00000000

// 若写锁计数为1,用[低16位]表示就是 1:
00000000 00000000 00000000 00000001
// 若读锁计数为1,用[高16位]表示就是 2^16:
00000000 00000001 00000000 00000000

// 若写锁计数+1 就是 2 = 1 + 1:
00000000 00000000 00000000 00000010
// 若读锁计数+1 就是 2^17 = 2^16 + 2^16
00000000 00000010 00000000 00000000

// 若当前存在写锁、读锁的计数分别为 1,获取写锁过程如下:
00000000 00000001 00000000 00000001 
00000000 00000000 11111111 11111111 // 掩码
00000000 00000000 00000000 00000001 // 与操作(&)后的结果
// 若当前存在写锁、读锁的计数分别为 1,获取读锁过程如下:
00000000 00000001 00000000 00000001 
00000000 00000000 00000000 00000001 // 无符号左移 16 位(>>>)的结果

明白了读写锁的计数表示以及获取过程,下面来看看其在代码中如何实现:

static final int SHARED_SHIFT = 16;

// 表示是[读锁]计数+1,即 2^16
static final int SHARED_UNIT = ( 1 << SHARED_SHIFT );

// 最大数量,即 2^16-1 = 65535
static final int MAX_COUNT = ( 1 << SHARED_SHIFT ) - 1;

// 掩码,表示 00000000 00000000 11111111 11111111
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;
}

3.读锁计数器

与写锁不同,读锁是共享锁,可以同时被多个线程持有。对于[多个线程]持有读锁的总重入计数可以通过 sharedCount 方法得到,但是对于[单个线程]持有读锁的重入计数如何表示呢?

首先来看 ReentrantReadWriteLock.HoldCounter 类,该类表示写锁的计数器,即每个线程对于读锁的重入计数。

static final class HoldCounter {
    // 若同一线程两次调用读锁的 lock 方法,则当前 count = 2 
    int count;

    // 使用 Id 而不是引用是为了避免保留垃圾。注意这是个常量。
    final long tid = Thread.currentThread().getId();
}

在 ReentrantReadWriteLock 中无法获得每个线程的读锁计数器,只能获取当前线程的计数器。这里通过 ThreadLocal 实现:

static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
    // 重写初始化方法
    // 关键 -> 调用该类的 get 方法时,可以获取到 HoldCounter 
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

该类在 Sync 初始化时被创建:

private transient ThreadLocalHoldCounter readHolds;
Sync() {
    readHolds = new ThreadLocalHoldCounter();
    setState(getState()); 
}

WriteLock 写锁

WriteLock ,即写锁,它是独占锁。同一时刻只能有一个线程持有它。

1.获取锁

这里 lock 操作为例进行分析:

public void lock() {
    // 调用过程:AQS.acquire -> tryAcquire
    sync.acquire(1);
}

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();

    // 取得[锁]的计数(开头介绍过,其包含了读锁和写锁)
    int c = getState();

    // 取得[写锁]的计数
    int w = exclusiveCount(c);

    if (c != 0) {
        // 关键 -> c != 0 and w == 0 ,说明[读锁]的计数不为 0
        // 判断当前线程未持有[写锁],返回 false
        if (w == 0 || current != getExclusiveOwnerThread()){
            return false;
        }

        // 判断[写锁]的计数是否超出最大数(65535)
        if (w + exclusiveCount(acquires) > MAX_COUNT){
            // 抛出异常...
        }
        setState(c + acquires);
        return true;
    }

    // 代码执行到这:说明读写锁都未被线程持锁(c=0)
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires)){
        return false;
    }
    setExclusiveOwnerThread(current);
    return true;
}

再来看看 writerShouldBlock 这个方法,它是实现公平写锁与非公平写锁的关键:

// NonfairSync
final boolean writerShouldBlock() {
    return false; 
}

// FairSync
final boolean writerShouldBlock() {
    return hasQueuedPredecessors();
} 
public final boolean hasQueuedPredecessors() {
    Node t = tail;
    Node h = head;
    Node s;
    return h != t && 
        ((s = h.next) == null || s.thread != Thread.currentThread());
}      

4.释放锁

public void unlock() {
    // AQS.release -> tryRelease
    sync.release(1);
}

// Sync
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively()){
        // 抛出异常...
    }

    // 判断释放操作后的写锁重入计数
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free){
        setExclusiveOwnerThread(null);
    }
    setState(nextc);
    return free;
}

ReadLock 读锁

ReadLock,即读锁。读锁具有以下特性:

  • 由于它是共享锁,可以被多个线程共同持有

  • 读锁的计数 = 所有持有读锁线程的重入计数。假设读锁被先后线程 t1,t2 持有,t1 的重入计数为 10,t2 的重入计数为 20,则读锁的计数 = 10+20。

若一个线程想要持有读锁需要满足以下条件:

  • WriteLock(写锁)没有被其他线程持有。
  • 当前线程持有写锁,由于它是独占锁,因此可以直接切换成读锁
  • 读锁的计数没有超出最大值(65535)。

1.获取锁

这里以 lock 为例来分析:

public void lock() {
    // 调用过程:AQS.acquireShared -> tryAcquireShared
    sync.acquireShared(1);
}

关键来看 tryAcquireShared:

// 在读锁只被一个线程持有的情况下使用
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;

// 最近(后)一个成功持有[读锁]的线程计数器。
private transient HoldCounter cachedHoldCounter;

// 当前线程的线程计数器
private transient ThreadLocalHoldCounter readHolds;

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();

    // 判断是否存在其他线程持有[写锁],若有则返回 -1 表示获取读锁失败
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) {
        return -1;
    }

    // 代码能执行到这里说明有两种情况:
    // ①当前线程持有[写锁],可以切换成[读锁]
    // ②没有线程持有[写锁],由于[读锁]是[共享锁],可以被多线程共同持有

    // [读锁]的重入计数
    int r = sharedCount(c);

    if (!readerShouldBlock() && r < MAX_COUNT 
        && compareAndSetState(c, c + SHARED_UNIT)) {

        // 若当前线程是第一个持有读锁的线程
        // 使用 firstReader,避免了查找 readHolds 
        if (r == 0) { 
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != current.getId()) {
                // 更新 cachedHoldCounter
                cachedHoldCounter = rh = readHolds.get();
            }else if (rh.count == 0) { 
                readHolds.set(rh);
            }

            rh.count++;
        }
        return 1;
    }

    // 获取读锁失败,放到循环里重试。
    return fullTryAcquireShared(current);
}

  • readerShouldBlock
// Fair
final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
}
public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;
    return h != t &&
    ((s = h.next) == null || s.thread != Thread.currentThread());
}


// Nonfair
final boolean readerShouldBlock() {
    return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    return (h = head) != null &&
    (s = h.next)  != null &&
    !s.isShared() &&
    s.thread != null;
}

  • fullTryAcquireShared
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) {

            } else {
                // 第一次循环时,rh 为空
                if (rh == null) {
                    // 判断 rh 是否是最后一个持有读锁的线程
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != current.getId()) {
                        // 不是的话,从 readHolds 获取
                        rh = readHolds.get();
                        if (rh.count == 0){
                            // 为 0 表示 rh 是 readHolds 刚初始化出来的
                            // 没有意义,所有要把它移除
                            readHolds.remove();
                        }
                    }
                }
                if (rh.count == 0){
                    return -1;
                }   
            }
        }

        if (sharedCount(c) == MAX_COUNT) {
            // 抛出异常...
        }

        // 通过 cas 给读锁的重入计数+1
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                }

                if (rh == null || rh.tid != current.getId()) {
                    rh = readHolds.get();
                } else if (rh.count == 0) {
                    readHolds.set(rh);
                }

                rh.count++;
                cachedHoldCounter = rh; 
            }
            return 1;
        }

        // 获取读锁失败,继续循环
    }
}

2.释放锁

public  void unlock() {
    // 调用过程: AQS.releaseShared -> tryReleaseShared
    sync.releaseShared(1);
}

protected final boolean tryReleaseShared(int unused) {
    // 当前线程是第一个持有读锁的线程?
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        // 判断重入计数
        if (firstReaderHoldCount == 1){
            firstReader = null;
        }else{
            firstReaderHoldCount--;
        }
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId()){
            rh = readHolds.get();
        }   
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0){
                // 抛出异常
            }
        }
        --rh.count;
    }

    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc)){
            return nextc == 0;
        }

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oxf

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值