【JUC源码解析】ReentrantReadWriteLock
简介
ReentrantReadWriteLock, 可重入读写锁,包括公平锁和非公平锁,相比较公平锁而言,非公平锁有更好的吞吐量,但可能会出现队列里的线程无限期地推迟一个或多个读线程或写线程的情况,因为后来的线程不必入队等待就可以竞争锁。
概述
读写锁,分为读锁(共享锁)和写锁(独占锁),有两种模式,包括公平模式和非公平模式。
非公平模式
读写锁的默认模式,竞争到锁的线程是无序的,因为,后来者可能先抢到线程,这大大增加了吞吐量。
对于写锁而言,如果当前线程具备获得锁的条件,则可以直接闯入获取锁。
对于读锁而言,如果排在等待队列head结点(最后一个已经获得锁的线程结点)后面的一个结点,等待的是写锁(isShared() == false),那么,当前线程应该入队等待;如果等待的是读锁,那么,当前线程可以闯入尝试获取锁。
公平模式
竞争到锁的线程是有序的(线程的到达顺序),即,对于写锁而言,一定是队列里等待最久的线程得到写锁;对读锁而言,一定是队列里等待最久的线程得到读锁。
锁降级
重入允许写锁降级为读锁。
具体是,线程先获取写锁,然后在获取读锁,最后释放写锁。
没有锁升级,即,从读锁升级为写锁是不允许的。
中断
读锁和写锁都支持锁获取期间的中断。
Condition
只能用于写锁。
应用
例一
public class CachedData {
Object data; // 缓存的数据
volatile boolean cacheValid; // 缓存有效性
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); // 读写锁 void processCachedData() {
rwl.readLock().lock(); // 获取读锁
if (!cacheValid) { // 如果缓存有效,直接使用数据,否则,释放读锁,并获取写锁
rwl.readLock().unlock(); // 释放读锁
rwl.writeLock().lock(); // 获取读锁
try {
if (!cacheValid) { // 再次判断数据有效性,读锁模式下,其他线程也许已经更新了缓存数据
data = new Object(); // 更新缓存
cacheValid = true; // 设置为有效
}
rwl.readLock().lock(); // 获取读锁
} finally {
rwl.writeLock().unlock(); // 释放写锁
}
} try {
use(data); // 使用缓存
} finally {
rwl.readLock().unlock(); // 释放读锁
}
} private void use(Object data) { // 使用数据
System.out.println(data);
}
}
例二
public class RWDictionary {
private final Map<String, Data> m = new TreeMap<String, Data>(); // 集合
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); // 读写锁
private final Lock r = rwl.readLock(); // 读锁
private final Lock w = rwl.writeLock(); // 写锁 public Data get(String key) { // 获取数据
r.lock(); // 获取读锁
try {
return m.get(key);
} finally {
r.unlock(); // 释放读锁
}
} public String[] allKeys() {
r.lock();
try {
return (String[]) m.keySet().toArray();
} finally {
r.unlock();
}
} public Data put(String key, Data value) { // 写数据
w.lock(); // 获取写锁
try {
return m.put(key, value);
} finally {
w.unlock(); // 释放写锁
}
} public void clear() {
w.lock();
try {
m.clear();
} finally {
w.unlock();
}
}
} class Data {
}
源码分析
属性
private final ReentrantReadWriteLock.ReadLock readerLock; // 读锁
private final ReentrantReadWriteLock.WriteLock writerLock; // 写锁
final Sync sync; // 内部类,继承子AQS
构造方法
public ReentrantReadWriteLock() { // 构造方法
this(false); // 默认非公平模式
} public ReentrantReadWriteLock(boolean fair) { // 构造方法,根据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; // 读锁
}
内部类Sync
属性
static final int SHARED_SHIFT = 16; // 高16位记录读锁的个数,低16位记录写锁的个数
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 读锁的单位,读锁减1,相当于减此单位值,写锁的单位(1)
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 锁的最大个数
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 写锁掩码,计算其个数时,与之相与,使高位为0,只计算低位 static int sharedCount(int c) {// 计算读锁的个数
return c >>> SHARED_SHIFT;
} static int exclusiveCount(int c) { // 计算写锁的个数
return c & EXCLUSIVE_MASK;
} static final class HoldCounter { // Sync的内部类,当前线程所持锁的计数器
int count = 0; // 个数
final long tid = getThreadId(Thread.currentThread()); // 线程ID
} static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() { // 初始值
return new HoldCounter();
}
} private transient ThreadLocalHoldCounter readHolds; // 当前线程所持有的可重入读锁的计数器,减小到0时删除 private transient HoldCounter cachedHoldCounter; // 缓存成功获取读锁的最后一个线程的计数器 private transient Thread firstReader = null; // 第一个获得读锁的线程
private transient int firstReaderHoldCount; // 第一个获得读锁线程所持锁的个数
构造方法
Sync() { // 构造方法
readHolds = new ThreadLocalHoldCounter();
setState(getState());
}
是否入队
abstract boolean readerShouldBlock(); // 如果当前线程尝试获取读锁,是否让其等待 abstract boolean writerShouldBlock(); // 如果当前线程尝试获取写锁,是否让其等待
NonfairSync
final boolean writerShouldBlock() {
return false; // 非公平模式下,获取写锁的线程可以直接插队
} final boolean readerShouldBlock() { // 非公平模式下,如果队列中排在head结点的线程等待的是写锁,则,当前线程应该入队等待;如果是读锁,则可以插队
return apparentlyFirstQueuedIsExclusive();
}
FairSync
final boolean writerShouldBlock() {
return hasQueuedPredecessors(); // 公平模式下,如果等待队列里有线程在等待,则,当前线程应该入队等待
} final boolean readerShouldBlock() {
return hasQueuedPredecessors(); // 公平模式下,如果等待队列里有线程在等待,则,当前线程应该入队等待
}
获取写锁
protected final boolean tryAcquire(int acquires) { // 获取写锁
Thread current = Thread.currentThread(); // 当前线程
int c = getState(); // 获取状态
int w = exclusiveCount(c); // 计算写锁的状态
if (c != 0) { // 如果当前状态不为0,说明有线程已经获得了读锁或写锁
if (w == 0 || current != getExclusiveOwnerThread()) // 如果有别的线程获取的是读锁(w == 0), 则返回false; 或者是有别的线程获取的是写锁,则查看那个线程是不是就是当前线程,如果不是,也返回false
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT) // 到这一步,说明当前线程已经获取过写锁,这次是重入,如果超出最大值,抛出一个错误
throw new Error("Maximum lock count exceeded");
setState(c + acquires); // 设置新的状态
return true;
}
if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 如果当前状态为0,说明还没有线程获取过读锁或写锁;然而,如果需要其等待,而不是让其获取,则返回false;或者CAS设置状态失败(被其他线程抢先了),也返回false
return false;
setExclusiveOwnerThread(current); // 设置当前线程为独占线程
return true; // 返回
}
释放写锁
protected final boolean tryRelease(int releases) { // 释放写锁
if (!isHeldExclusively()) // 如果当前线程没有持有锁,直接抛异常
throw new IllegalMonitorStateException();
int nextc = getState() - releases; // 计算新的状态
boolean free = exclusiveCount(nextc) == 0; // 计算写锁的空闲状态,如果没有线程持有写锁,表示空闲
if (free) // 如果是空闲状态
setExclusiveOwnerThread(null); // 独占者线程为null
setState(nextc); // 设置状态
return free; // 返回
}
获取读锁
protected final int tryAcquireShared(int unused) { // 获取读锁
Thread current = Thread.currentThread(); // 获得当前线程
int c = getState(); // 获取当前状态
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) // 如果别的线程持有写锁,直接返回失败(-1)
return -1;
int r = sharedCount(c); // 获取读锁的个数
if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 同时满足后面3个条件 1.不应该等待 2.小于最大值 3.CAS设置状态成功
if (r == 0) { // 如果读锁个数为0
firstReader = current; // 当前线程设置为第一个成功获得读锁的线程
firstReaderHoldCount = 1; // 个数设置为1
} else if (firstReader == current) { // 或者当前线程是第一个成功获得读锁的线程
firstReaderHoldCount++; // 个数加1
} else {
HoldCounter rh = cachedHoldCounter; // 或取最后一个成功获取读锁的线程计数器
if (rh == null || rh.tid != getThreadId(current)) // 如果为空,或者当前线程不是最后一个成功获取读锁的线程
cachedHoldCounter = rh = readHolds.get(); // 则从ThreadLocal里获取一个(新的),并赋给缓存器
else if (rh.count == 0) // 如果个数为0,则需要设置到线程变量里,因为release时,个数为0会删除
readHolds.set(rh);
rh.count++; // 个数加1
}
return 1; // 返回成功(1)
}
return fullTryAcquireShared(current); // 最后,完整版本的尝试获取
}
完整版本的尝试获取读锁
final int fullTryAcquireShared(Thread current) { // 完整版本的尝试获取读锁
HoldCounter rh = null; // 线程锁计数器
for (;;) {
int c = getState(); // 获取当前状态
if (exclusiveCount(c) != 0) { // 如果有线程持有写锁
if (getExclusiveOwnerThread() != current) // 判断持有写锁的线程是否是当前线程,如果不是,返回失败(-1)
return -1;
} else if (readerShouldBlock()) { // 如果应该等待
if (firstReader == current) {
} else { // 当前线程不是第一个获得读锁的线程
if (rh == null) { // 线程计数器为空
rh = cachedHoldCounter; // 获取缓存的,最后一次成功获得读锁的线程计数器
if (rh == null || rh.tid != getThreadId(current)) { // 为空,或者不是当前线程的计数器
rh = readHolds.get(); // 从线程变量里获取
if (rh.count == 0) // 如果个数为0
readHolds.remove(); // 从线程变量里删除
}
}
if (rh.count == 0) // 如果个数为0,返回失败(-1)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT) // 个数达到最大值,抛出一个错误
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) { // CAS设置新的状态
if (sharedCount(c) == 0) { // 如果读锁的个数为0
firstReader = current; // 当前线程设置为第一个成功获得读锁的线程
firstReaderHoldCount = 1; // 个数设置为1
} else if (firstReader == current) { // 或者当前线程是第一个成功获得读锁的线程
firstReaderHoldCount++; // 个数加1
} else {
if (rh == null)
rh = cachedHoldCounter; // 从缓存中获取计数器
if (rh == null || rh.tid != getThreadId(current)) // 为空,或者不是当前线程的计数器
rh = readHolds.get(); // 从线程变量里获取
else if (rh.count == 0) // 如果个数为0,则需要设置到线程变量里,因为release时,个数为0会删除
readHolds.set(rh);
rh.count++; // 个数加1
cachedHoldCounter = rh; // 缓存等待释放
}
return 1; // 返回成功(-1)
}
}
}
释放读锁
protected final boolean tryReleaseShared(int unused) { // 释放读锁
Thread current = Thread.currentThread(); // 获得当前线程
if (firstReader == current) { // 如果当前线程是第一个获的读锁的线程
if (firstReaderHoldCount == 1) // 如果所持锁的个数为1
firstReader = null; // 置空
else
firstReaderHoldCount--; // 否则,递减
} else { // 如果不是
HoldCounter rh = cachedHoldCounter; // 获取最后成功获得读锁的计数器,缓存的目的是避免从ThreadLocal里取值
if (rh == null || rh.tid != getThreadId(current)) // 如果为空,或者不是当前线程的计数器,也即,当前线程不是成功获得读锁的线程
rh = readHolds.get(); // 获取当前线程的技术器
int count = rh.count; // 获取当前线程所持锁的个数
if (count <= 1) { // 如果个数小于或等于1
readHolds.remove(); // 删除当前线程的计数器
if (count <= 0) // 如果小于0,抛出异常
throw unmatchedUnlockException();
}
--rh.count; // 计数减1
}
for (;;) {
int c = getState(); // 获取当前状态
int nextc = c - SHARED_UNIT; // 计算新的状态
if (compareAndSetState(c, nextc)) // CAS设置新的状态
return nextc == 0; // 返回
}
}
尝试获取写锁
final boolean tryWriteLock() { // 尝试获取写锁
Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取当前状态
if (c != 0) { // 如果有线程获取过锁,读锁或写锁
int w = exclusiveCount(c); // 计算写锁的个数
if (w == 0 || current != getExclusiveOwnerThread()) // 如果别的线程获取的是读锁,返回false; 或者有线程获取的是写锁,但那个线程不是当前线程,返回false
return false;
if (w == MAX_COUNT) // 超过最大值,抛出一个错误
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1)) // CAS 状态加1
return false;
setExclusiveOwnerThread(current); // 设置当前线程为独占者线程
return true;
}
尝试获取读锁
final boolean tryReadLock() { // 尝试获取读锁
Thread current = Thread.currentThread(); // 获取当前线程
for (;;) {
int c = getState(); // 获取当前状态
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) // 如果有别的线程获取了写锁,直接返回失败
return false;
int r = sharedCount(c); // 获取读锁的个数
if (r == MAX_COUNT) // 达到最大值,抛出一个错误
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) { // CAS设置状态的值
if (r == 0) { // 如果读锁的个数为0
firstReader = current; // 当前线程设置为第一个成功获得读锁的线程
firstReaderHoldCount = 1; // 个数设置为1
} else if (firstReader == current) { // 或者当前线程是第一个成功获得读锁的线程
firstReaderHoldCount++; // 个数加1
} else {
HoldCounter rh = cachedHoldCounter; // 从缓存中获取计数器
if (rh == null || rh.tid != getThreadId(current)) // 为空,或者不是当前线程的计数器
cachedHoldCounter = rh = readHolds.get(); // 则从ThreadLocal里获取一个(新的),并赋给缓存器
else if (rh.count == 0) // 如果个数为0,则需要设置到线程变量里,因为release时,个数为0会删除
readHolds.set(rh);
rh.count++; // 个数加1
}
return true; // 返回
}
}
}
实现读写锁
例子来自网上。
public final class ReadWriteLock {
private int readingReaders = 0; // 获得锁的读线程个数
private int writingWriters = 0; // 获得锁的写线程个数,最多1个
private int waitingWriters = 0; // 等待锁的写线程个数
private boolean preferWriter = true; // 偏向写线程,当写线程释放锁的时候,才为false public synchronized void readLock() throws InterruptedException { // 读锁
while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) { // 如果写线程获得锁,或者,等待锁的写线程个数大于0,而当前又偏向写线程的情况
wait(); // 则当前线程释放锁,进入等待队列,等待被唤醒,并重新抢占锁
}
readingReaders++; // 读线程个数加1
} public synchronized void readUnlock() { // 释放读锁
readingReaders--; // 读线程个数减1
preferWriter = true; // 偏向写线程
notifyAll(); // 唤醒所有等待的线程,包括读线程和写线程
} public synchronized void writeLock() throws InterruptedException { // 写锁
waitingWriters++; // 等待锁的写线程个数加1
try {
while (readingReaders > 0 || writingWriters > 0) { // 读锁或写锁个数大于0,则入队等待
wait();
}
} finally {
waitingWriters--; // 等待锁的写线程个数减1
}
writingWriters++; // 写线程个数加1
} public synchronized void writeUnlock() {
writingWriters--; // 线程个数减1
preferWriter = false; // 不偏向写线程
notifyAll(); // 唤醒所有等待的线程,包括读线程和写线程
}
}
行文至此结束。
尊重他人的劳动,转载请注明出处:http://www.cnblogs.com/aniao/p/aniao_rrwl.html
【JUC源码解析】ReentrantReadWriteLock的更多相关文章
- 【JUC源码解析】ScheduledThreadPoolExecutor
简介 它是一个线程池执行器(ThreadPoolExecutor),在给定的延迟(delay)后执行.在多线程或者对灵活性有要求的环境下,要优于java.util.Timer. 提交的任务在执行之前支 ...
- 【JUC源码解析】SynchronousQueue
简介 SynchronousQueue是一种特殊的阻塞队列,该队列没有容量. [存数据线程]到达队列后,若发现没有[取数据线程]在此等待,则[存数据线程]便入队等待,直到有[取数据线程]来取数据,并释 ...
- 【JUC源码解析】ForkJoinPool
简介 ForkJoin 框架,另一种风格的线程池(相比于ThreadPoolExecutor),采用分治算法,工作密取策略,极大地提高了并行性.对于那种大任务分割小任务的场景(分治)尤其有用. 框架图 ...
- 【JUC源码解析】DelayQueue
简介 基于优先级队列,以过期时间作为排序的基准,剩余时间最少的元素排在队首.只有过期的元素才能出队,在此之前,线程等待. 源码解析 属性 private final transient Reentra ...
- 【JUC源码解析】CyclicBarrier
简介 CyclicBarrier,一个同步器,允许多个线程相互等待,直到达到一个公共屏障点. 概述 CyclicBarrier支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后 ...
- 【JUC源码解析】ConcurrentLinkedQueue
简介 ConcurrentLinkedQueue是一个基于链表结点的无界线程安全队列. 概述 队列顺序,为FIFO(first-in-first-out):队首元素,是当前排队时间最长的:队尾元素,当 ...
- 【JUC源码解析】Exchanger
简介 Exchanger,并发工具类,用于线程间的数据交换. 使用 两个线程,两个缓冲区,一个线程往一个缓冲区里面填数据,另一个线程从另一个缓冲区里面取数据.当填数据的线程将缓冲区填满时,或者取数据的 ...
- Jdk1.6 JUC源码解析(12)-ArrayBlockingQueue
功能简介: ArrayBlockingQueue是一种基于数组实现的有界的阻塞队列.队列中的元素遵循先入先出(FIFO)的规则.新元素插入到队列的尾部,从队列头部取出元素. 和普通队列有所不同,该队列 ...
- Jdk1.6 JUC源码解析(6)-locks-AbstractQueuedSynchronizer
功能简介: AbstractQueuedSynchronizer(以下简称AQS)是Java并发包提供的一个同步基础机制,是并发包中实现Lock和其他同步机制(如:Semaphore.CountDow ...
随机推荐
- javascript学习2
上次我们了解到 JavaScript提供了一组以window为核心的对象,实现了对浏览器窗口的访问控制.JavaScript中定义了6种重要的对象: window对象 表示浏览器中打开的窗 ...
- rocket-console控制台安装
1.下载 github地址:https://github.com/apache/rocketmq-externals 2.选择稳定版本: 3.下载到本地: 环境需求 maven jdk ...
- HTML5手机端拍照上传
1.accept="image/*" capture="camera" 自动调用手机端拍照功能 accept="image/*" captu ...
- 在金融服务计算中,必须要使用BigDecimal
在Java程序开发过程中,比较初级(工作经验受限)的开发人员,把注意力全部放在了一些高大上的新技术中,往往忽略了一些初级问题.. 金融服务系统中,对金额的敏感至关重要,账户余额.还款金额.代收本金.代 ...
- TCL-事务
一.含义事务:一条或多条sql语句组成一个执行单位,一组sql语句要么都执行要么都不执行二.特点(ACID)A 原子性:一个事务是不可再分割的整体,要么都执行要么都不执行C 一致性:一个事务可以使数据 ...
- Oracle中函数的使用
1.decode () 例子:它的写法如下decode('a','b','c','d'),其中a,b,c,d可以是其他函数也可以是数值,依据我们自己的情况来使用,它的含义是如果a=b,那么结果显示 ...
- MAC 相关
1.找回个人收藏下的消失项,如文稿等 点击个人收藏中下的任意项,如桌面.下载等,按住Command+上箭头,出现如下界面,拖住消失项添加到个人收藏即可
- Vue packages version conflicts 错误修复
我们在使用Vue作为weex中的前端框架的开发过程中,某次 npm start 遇到了如下的错误: Vue packages version mismatch: - vue@2.5.16 - vue- ...
- python3爬虫-爬取B站排行榜信息
import requests, re, time, os category_dic = { "all": "全站榜", "origin": ...
- C++的六个函数
一.构造函数 在C++中,构造函数是六个函数中的第一个,当一个对象被创建时,在它的整个周期中,是一个由生到死的 过程,即构造函数创建对象,析构函数析构对象.在对象被创建时,调用构造函数创建一个对象,这 ...