聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁
上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和基本的方法,显示了怎样实现的锁降级。可是以下几个问题没说清楚,这篇补充一下
1. 释放锁时的优先级问题。是让写锁先获得还是先让读锁先获得
2. 是否同意读线程插队
3. 是否同意写线程插队,由于读写锁一般用在大量读,少量写的情况,假设写线程没有优先级,那么可能造成写线程的饥饿
关于释放锁后是让写锁先获得还是让读锁先获得,这里有两种情况
1. 释放锁后,请求获取写锁的线程不在AQS队列
2. 释放锁后。请求获取写锁的线程已经AQS队列
假设是第一种情况。那么非公平锁的实现下,获取写锁的线程直接尝试竞争锁也不用管AQS里面先来的线程。获取读锁的线程仅仅推断是否已经有线程获得写锁(既Head节点是独占模式的节点),假设没有,那么就不用管AQS里面先来的准备获取读锁的线程。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
}
在公平锁的情况下。获取读锁和写锁的线程都推断是否已经或先来的线程再等待了。假设有,就进入AQS队列等待。
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
对于另外一种情况,假设准备获取写锁的线程在AQS队列里面等待。那么实际是遵循先来先服务的公平性的,由于AQS的队列是FIFO的队列。所以获取锁的线程的顺序是跟它在AQS同步队列里的位置有关系。
以下这张图模拟了AQS队列中等待的线程节点的情况
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvSVRlcl9aQw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">
1. Head节点始终是当前获得了锁的线程
2. 非Head节点在竞争锁失败后。acquire方法会不断地轮询。于自旋不同的是,AQS轮询过程中的线程是堵塞等待。
所以要理解AQS的release释放动作并非让兴许节点直接获取锁。而是唤醒兴许节点unparkSuccessor()。
真正获取锁的地方还是在acquire方法,被release唤醒的线程继续轮询状态,假设它的前驱是head,而且tryAcquire获取资源成功了,那么它就获得锁
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
} final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3. 图中Head之后有3个准备获取读锁的线程,最后是1个准备获取写锁的线程。
那么假设是AQS队列中的节点获取锁
情况是第一个读锁节点先获得锁。它获取锁的时候就会尝试释放共享模式下的一个读锁。假设释放成功了,下一个读锁节点就也会被unparkSuccessor唤醒,然后也会获得锁。
假设释放失败了。那就把它的状态标记了PROPAGATE,当它释放的时候。会再次取尝试唤醒下一个读锁节点
假设后继节点是写锁。那么就不唤醒
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
} private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
} private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
AQS的FIFO队列保证了在大量读锁和少量写锁的情况下,写锁也不会饥饿。
关于读锁能不能插队的问题,非公平性的Sync提供了插队的可能,可是前提是它在tryAcquire就成功获得了。假设tryAcquire失败了,它就得进入AQS队列排队。也不会出现让写锁饥饿的情况。
关于写锁能不能插队的情况,也是和读锁一样,非公平的Sync提供了插队的可能,假设tryAcquire获取失败,就得进入AQS等待。
最后说说为什么Semaphore和ReentrantLock在tryAcquireXX方法就实现了非公平性和公平性,而ReentrantReadWriteLock却要抽象出readerShouldBlock和writerShouldBlock的方法来单独处理公平性。
abstract boolean readerShouldBlock();
abstract boolean writerShouldBlock();
原因是Semaphore仅仅支持共享模式,所以它仅仅须要在NonfairSync和FairSync里面实现tryAcquireShared方法就能实现公平性和非公平性。
ReentrantLock仅仅支持独占模式,所以它仅仅须要在NonfairSync和FairSync里面实现tryAcquire方法就能实现公平性和非公平性。
而ReentrantReadWriteLock即要支持共享和独占模式。又要支持公平性和非公平性。所以它在基类的Sync里面用tryAcquire和tryAcquireShared方法来区分独占和共享模式。
在NonfairSync和FairSync的readerShouldBlock和writerShouldBlock里面实现非公平性和公平性。
聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁的更多相关文章
- 聊聊高并发(二十)解析java.util.concurrent各个组件(二) 12个原子变量相关类
这篇说说java.util.concurrent.atomic包里的类,总共12个.网上有非常多文章解析这几个类.这里挑些重点说说. watermark/2/text/aHR0cDovL2Jsb2cu ...
- 谈论高并发(三十)解析java.util.concurrent各种组件(十二) 认识CyclicBarrier栅栏
这次谈话CyclicBarrier栅栏,如可以从它的名字可以看出,它是可重复使用. 它的功能和CountDownLatch类别似,也让一组线程等待,然后开始往下跑起来.但也有在两者之间有一些差别 1. ...
- 聊聊高并发(四十)解析java.util.concurrent各个组件(十六) ThreadPoolExecutor源代码分析
ThreadPoolExecutor是Executor运行框架最重要的一个实现类.提供了线程池管理和任务管理是两个最主要的能力.这篇通过分析ThreadPoolExecutor的源代码来看看怎样设计和 ...
- 聊聊高并发(二十五)解析java.util.concurrent各个组件(七) 理解Semaphore
前几篇分析了一下AQS的原理和实现.这篇拿Semaphore信号量做样例看看AQS实际是怎样使用的. Semaphore表示了一种能够同一时候有多个线程进入临界区的同步器,它维护了一个状态表示可用的票 ...
- 聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁
这篇讲讲ReentrantReadWriteLock可重入读写锁,它不仅是读写锁的实现,而且支持可重入性. 聊聊高并发(十五)实现一个简单的读-写锁(共享-排他锁) 这篇讲了怎样模拟一个读写锁. 可重 ...
- 018-并发编程-java.util.concurrent.locks之-ReentrantReadWriteLock可重入读写锁
一.概述 ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程.写线程和写线程同时访问.相对 ...
- 聊聊高并发(三十九)解析java.util.concurrent各个组件(十五) 理解ExecutorService接口的设计
上一篇讲了Executor接口的设计,目的是将任务的运行和任务的提交解耦.能够隐藏任务的运行策略.这篇说说ExecutorService接口.它扩展了Executor接口,对Executor的生命周期 ...
- 聊聊高并发(二十四)解析java.util.concurrent各个组件(六) 深入理解AQS(四)
近期总体过了下AQS的结构.也在网上看了一些讲AQS的文章,大部分的文章都是泛泛而谈.又一次看了下AQS的代码,把一些新的要点拿出来说一说. AQS是一个管程.提供了一个主要的同步器的能力,包括了一个 ...
- 聊聊高并发(四十四)解析java.util.concurrent各个组件(二十) Executors工厂类
Executor框架为了更方便使用,提供了Executors这个工厂类.通过一系列的静态工厂方法.能够高速地创建对应的Executor实例. 仅仅有一个nThreads參数的newFixedThrea ...
随机推荐
- SqlHelper初探之二
在上一篇简单的介绍了sqlhelper的基本知识,接下来就让我们进一步学习他的实践过程. 首先:我们要明白的一件事Sqlhelper不是写出来的,而是在D层的代码中提炼出来的?那么就会反问一句“D层中 ...
- Net Core子应用由于配置引起IIS错误500.19
Asp.Net Core子应用由于配置中重复添加模块会引起IIS错误500.19 ASP.NET Core已经从IIS中解耦,可以作为自宿主程序运行,不再依赖IIS. 但我们还是需要强大的IIS作为前 ...
- windows下RabbitMQ 监控
RabbitMQ的监控很简单,网上也有很多资料,但是大都不详细,让人云里雾里,我这里详细总结下. RabbitMQ本身提供了一个web的监控页面,只需要简单的几部命令行就可以访问这个页面了. 1.打开 ...
- jquery ajax验证用户名是否存在(后台spring mvc)
controller层 @ResponseBody @RequestMapping(value = "/user/isExist", produces = "applic ...
- SVN权限解析规则详解(转)
首先创建一个版本库后,会生成最初的目录结构和基本的配置文件,本文主要分析“authz”文件的内容:我们先抛开alias和groups不谈,将重点放在路径的权限配置上. 一. 权限格式 svn权限的基本 ...
- FOJ 2170 花生的序列 dp
题目链接:http://acm.fzu.edu.cn/problem.php? pid=2170 贴个baka爷的代码留念.. 数据出的有问题.输入的字符串长度不超过1000 #include< ...
- Webserver管理系列:5、利用MSConfig排查木马
木马程序最喜欢去的地方有两个一个是服务里面,一个是启动里面.利用msconfig我们能够高速的找到可疑程序. 在命令行中输入msconfig回车 选择服务项: 这里面的服务有非常多我们非常难排查,我告 ...
- java android面试题分析总结
本文参考多处,一并感谢! http://www.blogjava.net/fanyingjie/archive/2007/06/27/126467.aspx http://baike.baidu.co ...
- Common Lisp Style Guide - Ariel Networks Labs
Common Lisp Style Guide - Ariel Networks Labs Common Lisp Style Guide
- System单元对所有与COM相关的声明就这么多,需要倒背如流
是首先是VM表,但是和COM相关的函数地址都废弃了,这几个VM函数具体放在哪里,还得在研究: { Virtual method table entries } vmtSelfPtr = -; vmtI ...