java.util.concurrent源码分析(四)ReentrantReadWriteLock实现

1、ReentrantReadWriteLock介绍

(1)ReentrantReadWriteLock与ReentrantLock

ReentrantLock实现了标准的互斥操作, 也就是一次只能有一个线程持有锁, 也即所谓独占锁的概念. 显然这个特点在一定程度上面降低了吞吐量, 实际上独占锁是一种保守的锁策略, 在这种情况下任何”读/读”, “写/读”, “写/写”操作都不能同时发生.但是同样需要强调的一个概念是, 锁是有一定的开销的, 当并发比较大的时候, 锁的开销就比较客观了。 所以如果可能的话就尽量少用锁, 非要用锁的话就尝试看能否改造为读写锁.

ReadWriteLock描述的是: 一个资源能够被多个读线程访问, 或者被一个写线程访问,但是不能同时存在读写线程。

(2)ReentrantReadWriteLock中的state

1、传统的独占锁、共享锁
    在ReentrantLock中该字段用来描述有多少线程获持有锁;
    在独占锁的时代这个值通常是0或者1;
    在共享锁的时代就是持有锁的数量;
2、ReadWriteLock的独占锁、共享锁
    ReadWriteLock的读、写锁是相关但是又不一致的, 所以需要两个数来描述读锁(共享锁)和写锁(独占锁)的数量;
    显然现在一个state就不够用了. 于是在ReentrantReadWrilteLock里面将这个字段一分为二, 高位16位表示共享锁的数量, 低位16位表示独占锁的数量;
    共享锁和独占锁的数量最大只能是2^(16-1)=65536;

定义:

static final int SHARED_SHIFT   = 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;  

(3)ReentrantReadWriteLock的内部结构

ReentrantReadWriteLock-->ReadWriteLock
NonfairSync/FairSync-->Sync-->AbstractQueuedSynchronizer-->AbstractOwnableSynchronizer
ReadLock/WriteLock-->Lock       
注:NonfairSync,FairSync,Sync,ReadLock和WriteLock是ReentrantReadWriteLock的内部类。

2、ReentrantReadWriteLock的创建

//ReentrantReadWriteLock构造方法:无参、有参
public ReentrantReadWriteLock() {
    this(false);
}
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}
//可以看到与ReentrantLock一样, 锁的主体部分依然是Sync(FairSync/NonFaireSync), 但是增加了readerLock和writerLock两个实例。

//ReadLock、WriteLock构造方法
protected ReadLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
}
protected WriteLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
}
//看ReadLock和WriterLock的构造函数可知两个实例共享一个sync实例。

3、写入锁和读取锁

(1)写入锁的获取

public void lock() {
    sync.acquire(1);
}
//与ReentrantLock.lock()一致,分为公平锁和非公平锁(sync默认为非公平锁)

(2)读取锁的获取

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

//AQS的acquireShared方法
public final void acquireShared(int arg) {
   if (tryAcquireShared(arg) < 0)
       doAcquireShared(arg);
}

流程:

1. 如果写线程持有锁(也就是独占锁数量不为0), 并且独占线程不是当前线程, 那么就返回失败,因为不允许写入线程获取锁的同时获取读取锁。否则进行2;
2. 如果读线程请求锁数量达到了65535(包括重入锁), 那么就跑出一个错误Error, 否则进行3;
3. 如果读线程不用等待, 并且增加读取锁状态数成功, 那么就返回成功, 否则进行4;
4. fullTryAcquireShared(内部包含一个无限循环, 实际上是过程3的不断尝试直到CAS计数成功或者被写入线程占有锁);

(3)读取锁的释放

读取锁的释放其实就是一个不断尝试的CAS操作, 直到修改状态成功。

(4)HoldCounter

a. HoldCounter保存了当前线程持有共享锁(读取锁)的数量, 包括重入的数量. 那么这个数量就必须和线程绑定在一起.
b. 在Java里面将一个对象和线程绑定在一起, 就只有ThreadLocal才能实现了. 所以毫无疑问HoldCounter就应该是绑定到线程上的一个计数器。

static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = Thread.currentThread().getId();
}

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

(5)读写锁的应用

使用ReentrantReadWriteLock可以推广到大部分读, 少量写的场景, 因为读线程之间没有竞争, 所以比起sychronzied, 性能好很多。

猜你喜欢

转载自blog.csdn.net/hehuanchun0311/article/details/79711771