ReentrantReadWriteLock
1. 特点:ReentrantReadWriteLock是一个读写锁,它提供了一个读锁和一个写锁,读锁用于只读操作,而写锁用于写入操作,读操作可以并行进行,而写操作则是互斥的。读锁和写锁的分离在一些写少读多的应用中可以带来性能上的提升
2. 读锁与写锁的约束关系
1) 当任一线程持有写锁或读锁时,其他线程不能获得写锁;
2) 当任一线程持有写锁时,其他线程不能获取读锁;
3) 多个线程可以同时持有读锁。
3. 优点:实现并发的读和互斥的写,达到很多操作并行的目地,从而提高性能。
4. 使用:
1) 创建一个读锁和一个写锁
2) 通过读锁实现并发读
3) 通过写锁实现互斥写
锁的获取顺序
1. 非公平模式:
1) 非公平模式不按照线程请求锁的顺序分配锁,而是当前请求锁的线程和等待队列中的线程一起竞争锁。
2) 连续竞争的非公平锁可能会导致等待队列中的线程长时间等待,但吞吐量要高于公平锁。
3) 非公平模式中,如果等待队列的头节点是写者线程(当前执行的可能是一个写者线程或者是多个读者线程),那么新到的读者线程将进入等待队列中阻塞。
4) 队头的写者线程,在当前正在执行的一个写者线程或者多个读者线程执行完成后,就会得到执行(如果存在新到写者线程,则需要竞争),防止等待队列中的写者线程一直等待。
2. 公平模式:
1) 公平模式采用近似FIFO的策略获取锁
2) 当一个线程释放了锁后,等待队列中等待时间最长的线程(单个写线程或者多个读线程)将获取锁(写锁或者读锁)。
3) 由于公平模式采用FIFO的策略获取锁,因此不存在写线程一直等待的问题。
重入
允许写者和读者按照ReentrantLock的方式多次获取读锁或写锁
1. 获取写锁的线程可以再次获取读锁:即写锁可以降级为读锁
2. 获取读锁的线程不能再次获取写锁:即读锁不可以升级为写锁
Condition支持
1. 读锁不支持Condition:readLock().newCondition()会抛出Unsupported OperationException
2. 写锁支持Condition:对于写入锁来说,该实现的行为与 ReentrantLock.newCondition()提供的Condition 实现对ReentrantLock所做的行为相同
ReentrantReadWriteLock的实现
1. 同步状态
1) ReentrantReadWriteLock是将AQS中同步状态的整型变量分为了两个部分来实现的
Ø 低位的16位用于保存写锁状态;
Ø 高位的16位用于保存读锁状态;
2) sharedCount:用于获取共享锁(读锁)数量,因为读锁是共享锁;
3) exclusiveCount:用于获取排它锁(写锁)数量,因为写锁是排它锁。
2. 读锁与写锁
1) 读锁
a) Lock():调用的是AQS中的acquireShared方法。
b) Unlock():调用的是AQS中的releaseShared方法。
c) 采用的是AQS的共享模式,且不支持newCondition操作
2) 写锁
a) Lock():调用AQS的acquire方法;
b) Release():调用AQS的忍了阿萨我方法;
c) 写锁采用的是AQS的排它模式,且支持newCondition 操作;
3. 非公平模式
1) 写锁:获取锁
1. 获取当前同步状态c
2. 通过同步状态C获取写锁的数量
3. C!=0 表示所以被占用(可能是写锁,也可能是读锁)
4. W=0 表当前占用的是读锁,返回false
Ø 若w!=0 表示当前占用的是写锁,需进一步判断,是否是自己占用了这个写锁。
Ø 若不是返回false
5. 检查是否超过锁的上限
6. 修改写锁数
7. (写线程永不阻塞)C=0 表示当前没有锁被占用,故修改锁的状态、将锁的拥有者声明为自己。
2) 写锁:释放锁
a) 检测:判断是否是锁的拥有者
b) 修改锁的状态
c) 若锁为0,表示释放锁
d) 将锁的拥有者置为空
3) 读锁:获取锁
1. 第一步获得所得状态
2. 若已有排他锁(写锁)占用并且写锁拥有者不是当前线程,怎返回-1;
上面检查完后,至此说明当前占用的是读锁,读锁是可以并发的。
3. 获得共享锁的个数,即读锁的个数;
4. 由于读锁不应该让写锁始终等待,故需要判断是否应该阻塞。
5. 如果readerShouldBlock返回false,且读锁数量小于MAX_COUNT,就可以尝试将读锁数量修改到c + SHARED_UNIT
6. 如果上步骤4中的条件都通过了,就表示读锁获取成功了,接下来的操作就是设置计数信息了。
7. 如果步骤4中条件失败,表示获取读锁失败,阻塞并等待唤醒。
4. 公平模式
1) 写锁:与非公平锁类似,唯一的差异就在于在tryAcquire中对writerShouldBlock的判断:
hasQueuedPredecessors:用于判断等待队列中是否存在等待线程,如果存在等待线程,则厚道的线程将进入等待队列中阻塞等待。
2) 读锁:同上,在是否应阻塞的判断上不一样,也是唯一区别。