并发编程(4)——AbstractQueuedSynchronizer
AQS
内部类Node
等待队列是CLH有锁队列的变体。
waitStatus的几种状态:
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
以下面的测试程序为例,简单介绍一下同步队列的变化:
@Test
public void test() {
CountDownLatch countDownLatch = new CountDownLatch(1);
ReentrantLock lock = new ReentrantLock();
try {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
}
}, "线程 " + i
).start();
}
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// lock.unlock();
}
我们发现,ReentrantLock的lock方法如下:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
由于是独占的获取,因此只有一个线程会通过CAS成功获取state,因此其它四个线程都会进入acquire(1)方法。acquire(int arg)是AQS的模板方法,方法内容如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
以非公平锁为例,tryAcquire实际调用nonfairTryAcquire.该方法可以看出,首先还是通过CAS来获取state,如果是owner是之前的那个线程的话,允许重入,acquire加acquires。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
继续回到刚才的acquire方法,会发现tryAcquire方法返回false,调用addWaiter方法:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
假设最开始是线程0获取了state,后面来的依次是线程1、线程2、线程3、线程4.
线程1进入addWaiter方法,tail为空,进入enq方法,这里会初始化AQS中的head和tail,例子里的话head是一个new Node对象,tail的Node对象是new Node(“线程1”, mode)对象。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
继续,执行完addWaiter方法之后会进入acquireQueued方法:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 找到node的前辈节点
final Node p = node.predecessor();
// 如果线程0不释放,则该不会进入
// 如果线程0释放state,并且p是head,也就是同步队列中的第一个任务,这个时候获取state成功,将node设置为AQS的head,返回false,结束acquire方法。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 第一个判断,判断node的前辈节点是否为-1或者大于0,否则设置状态为-1,再下一次循环时,返回true进入第二个判断
// 第二个判断,将node对应的线程park,即设置为wait状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
其余的线程2/3/4依次在同步队列上,类似于:
+-----------+ +-----------+ + -----------+ + -----------+ +-----------+
| head | | 线程1 | | 线程2 | | 线程3 | | 线程4|
+-----------+ +-----------+ + -----------+ + -----------+ +-----------+
以下面测试程序为例,再看unlock方法(顺便提一下,idea调试多线程需要将断点处的all改为thread, 程序中的countdownlatch是为了不让test线程结束,导致无法调试)调试时看到一个线程进入release方法,其余四个线程处于wait状态,说明程序正确了。
@Test
public void test() {
CountDownLatch countDownLatch = new CountDownLatch(1);
ReentrantLock lock = new ReentrantLock();
try {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if ( lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}, "线程 " + i
).start();
}
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
}
讲完了lock()方法,再看unlock()方法,调用release(int arg)方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease(arg)不再赘述,不过是释放获得的许可,将state设置为0(一般情况下,有些是重入,需要多调用几次unlock才行),置空独占线程。
进入if内部,调用unparkSuccessor方法
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
正常情况下,唤醒同步队列中的第一个任务线程
acquireShared
上面讲的是独占获取,接下来看一下共享获取
这里以ReentrantReadWriteLock为例
简单介绍一下内部类,包含一个同步器Sync,以及公平及非公平类FairSync与NonfairSync,ReadLock和WriteLock
因为读锁非独占,因此lock方法对应的是sync.tryAcquireShared(1),写锁则相反。
其他
AQS使用了模板方法设计模式。
并发编程(4)——AbstractQueuedSynchronizer的更多相关文章
- 并发编程 20—— AbstractQueuedSynchronizer 深入分析
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- Java并发编程系列-AbstractQueuedSynchronizer
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/10566625.html 一.概述 AbstractQueuedSynchronizer简 ...
- Java并发编程(2) AbstractQueuedSynchronizer的设计与实现
一 前言 上一篇分析AQS的内部结构,其中有介绍AQS是什么,以及它的内部结构的组成,那么今天就来分析下前面说的内部结构在AQS中的具体作用(主要在具体实现中体现). 二 AQS的接口和简单示例 上篇 ...
- Java并发编程(2) AbstractQueuedSynchronizer的内部结构
一 前言 虽然已经有很多前辈已经分析过AbstractQueuedSynchronizer(简称AQS,也叫队列同步器)类,但是感觉那些点始终是别人的,看一遍甚至几遍终不会印象深刻.所以还是记录下来印 ...
- 并发编程 01—— ThreadLocal
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- 并发编程 02—— ConcurrentHashMap
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- 并发编程 04——闭锁CountDownLatch 与 栅栏CyclicBarrier
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- 并发编程 05—— Callable和Future
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- 并发编程 06—— CompletionService :Executor 和 BlockingQueue
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- 并发编程 10—— 任务取消 之 关闭 ExecutorService
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
随机推荐
- 设计模式-抽象工厂模式(AbstractFactory)
抽象工厂模式是向客户端提供一个接口(FruitFactory),,使得客户端在不必指定产品的具体类型的情况下,能够创建多个产品族(NorthFruit.SouthFruit)的产品对象. 角色和职责: ...
- Git 安装教程(2.21.0)
获取安装包地址 1.官网(较慢):https://git-scm.com/downloads 2.下载链接:http://down-ww3.newasp.net/pcdown/soft/soft1/g ...
- linux命令积累
lsof -i: //根据端口号查相关信息 //杀进程 ps -ef|grep appName //根据进程名称查找相关信息 grep -r "关键词" 目录 //在制定目录下根据 ...
- c++学习书籍推荐《C++ Primer Plus中文版(第6版)》下载
百度云及其他网盘下载地址:点我 编辑推荐 一本经久不衰的C++畅销经典教程:一本支持C++11新标准的程序设计图书. 它被誉为“开发人员学习C++的教程,没有之一”! Amazon网站“Langua ...
- markdown的博客
测试一下markdown写博客 function firstProgram() { console.log("This is my first markdown blog"); }
- Windows 下配置 Logstash 为后台服务
到目前为止,logstash 没有给出官方的,在 windows 系统中作为后台服务运行的方式.本文将介绍如何使用第三方工具 nssm 让 logstash 作为后台服务运行在 windows 中.说 ...
- C程序中文标点惹的祸,你可长点儿心吧
想必你在编程时,即使写了一个很小很简短的程序,本以为编译一定不会有问题,自信满满,结果编译完了,编译器 给了几个大大的error,比如:error C2018: unknown character'0 ...
- sublime 如何安装插件实现高效输入,如何支持markdown 语法,并实时预览
啊,一直想鼓捣markdown的语法,但是配置什么的有点麻烦.不过用起来的话,真心顺手.无需考虑格式与语法点来点去影响效率, 用心去搬砖,用脚修bug 一.初识 Package Control 首先, ...
- 个人永久性免费-Excel催化剂功能第34波-提取中国身份证信息、农历日期转换相关功能
这两天又被刷朋友圈,又来了一个自主研发红芯浏览器,国产啊国产,这是谁的梦.就算国产了,自主了,无底线的夸大吹嘘无道德,企业如是,国家如是,大清已亡!再牛B的技术落在天天删敏感信息.无法治.无安全感可言 ...
- Docker部署web环境之Lanmp
1. 案例一 整套项目多容器分离通过docker-compose部署lanmp环境 中方文档参考网址: docker/kubernets网址 http://www.dockerinfo.net/doc ...