并发编程(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—— 闭 ...
随机推荐
- kafka迁移topic
1. 准备移动 这里假设要移动的topic名称为:topicA.topicB vim topics-to-move.json {"topics": [{"topic&qu ...
- 走进python
python史 1.python之父 Guido van Rossum 2.python的优缺点 优点:开发效率高,可跨平台,可嵌入,可扩展,优雅简洁 缺点:运行稍慢,代码不能加密,不能实现真正的多线 ...
- kafka入门(一)简介
1 什么是kafk Apache kafka是消息中间件的一种,在开始学习之前,先简单的解释一下什么是消息中间件. 举个例子,生产者消费者,生产者生产鸡蛋,消费者消费鸡蛋,生产者生产一个鸡蛋,消费者就 ...
- LinkedHashMap如何保证顺序性
一. 前言 先看一个例子,我们想在页面展示一周内的消费变化情况,用echarts面积图进行展示.如下: 我们在后台将数据构造完成 HashMap<String, Integer> map ...
- 热度3年猛增20倍,Serverless&云开发的技术架构全解析
『 作为一个不断发展的新兴技术, Serverless 热度的制高点已然到来.』 或许,Google Trends 所显示的 3 年猛增 20 倍的" Serverless " 搜 ...
- 关于String重写的hashcode的代码分析
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = valu ...
- java算法题每日一练01,java入门简单算法题小练
1.给数组做反序 public class Ak01 { public static void main(String[] args) { int[] a = new int[]{22,48,41,2 ...
- [原创]MySQL数据库查询和LVM备份还原学习笔记记录
一.查询语句类型: 1)简单查询 2)多表查询 3)子查询 4)联合查询 1)简单查询: SELECT * FROM tb_name; SELECT field1,field2 FROM tb_nam ...
- MySql的数据库优化到底优化啥了都(3)
嘟嘟在上两个文章里面简单粗糙的讲了讲关于MySql存储引擎的一些特性以及选择.个人感觉如果面试官给我机会的话,至少能说个10分钟了吧.只可惜有时候生活就是这样:骨感的皮包骨头了还在那美呢.牢骚两句,北 ...
- lambda匿名函数,sorted(),filter(),map(),递归函数
1.lambda匿名函数 为了解决一些简单的需求而设计的一句话函数 #计算n的n次方 def func(n): return n**n print(func(10)) f = lambda n: n* ...