深入浅出 Java Concurrency (7): 锁机制 part 2 AQS[转]
在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复杂的一个基础类:java.util.concurrent.locks.AbstractQueuedSynchronizer。
AQS
AbstractQueuedSynchronizer,简称AQS,是J.U.C最复杂的一个类,导致绝大多数讲解并发原理或者实战的时候都不会提到此类。但是虚心的作者愿意借助自己有限的能力和精力来探讨一二(参考资源中也有一些作者做了部分的分析。)。
首先从理论知识开始,在了解了相关原理后会针对源码进行一些分析,最后加上一些实战来描述。
![]()
上面的继承体系中,AbstractQueuedSynchronizer是CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AbstractQueuedSynchronizer是Lock/Executor实现的前提。公平锁、不公平锁、Condition、CountDownLatch、Semaphore等放到后面的篇幅中说明。
完整的设计原理可以参考Doug Lea的论文 The java.util.concurrent Synchronizer Framework ,这里做一些简要的分析。
基本的思想是表现为一个同步器,支持下面两个操作:
获取锁:首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败,也就是说如果是独占锁就可能阻塞,如果是共享锁就可能失败。另外如果是阻塞线程,那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态,并且如果进了队列就从队列中移除。
while(synchronization state does not allow acquire){
enqueue current thread if not already queued;
possibly block current thread;
}
dequeue current thread if it was queued;
释放锁:这个过程就是修改状态位,如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程。
update synchronization state;
if(state may permit a blocked thread to acquire)
unlock one or more queued threads;
要支持上面两个操作就必须有下面的条件:
- 原子性操作同步器的状态位
- 阻塞和唤醒线程
- 一个有序的队列
目标明确,要解决的问题也清晰了,那么剩下的就是解决上面三个问题。
状态位的原子操作
这里使用一个32位的整数来描述状态位,前面章节的原子操作的理论知识整好派上用场,在这里依然使用CAS操作来解决这个问题。事实上这里还有一个64位版本的同步器(AbstractQueuedLongSynchronizer),这里暂且不谈。
阻塞和唤醒线程
标准的JAVA API里面是无法挂起(阻塞)一个线程,然后在将来某个时刻再唤醒它的。JDK 1.0的API里面有Thread.suspend和Thread.resume,并且一直延续了下来。但是这些都是过时的API,而且也是不推荐的做法。
在JDK 5.0以后利用JNI在LockSupport类中实现了此特性。
LockSupport.park()
LockSupport.park(Object)
LockSupport.parkNanos(Object, long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(Object, long)
LockSupport.parkUntil(long)
LockSupport.unpark(Thread)
上面的API中park()是在当前线程中调用,导致线程阻塞,带参数的Object是挂起的对象,这样监视的时候就能够知道此线程是因为什么资源而阻塞的。由于park()立即返回,所以通常情况下需要在循环中去检测竞争资源来决定是否进行下一次阻塞。park()返回的原因有三:
其实第三条就决定了需要循环检测了,类似于通常写的while(checkCondition()){Thread.sleep(time);}类似的功能。
有序队列
在AQS中采用CHL列表来解决有序的队列的问题。
AQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。
对于入队列(enqueue):采用CAS操作,每次比较尾结点是否一致,然后插入的到尾结点中。
do {
pred = tail;
}while ( !compareAndSet(pred,tail,node) );
对于出队列(dequeue):由于每一个节点也缓存了一个状态,决定是否出队列,因此当不满足条件时就需要自旋等待,一旦满足条件就将头结点设置为下一个节点。
while (pred.status != RELEASED) ;
head = node;
实际上这里自旋等待也是使用LockSupport.park()来实现的。
AQS里面有三个核心字段:
private volatile int state;
private transient volatile Node head;
private transient volatile Node tail;
其中state描述的有多少个线程取得了锁,对于互斥锁来说state<=1。head/tail加上CAS操作就构成了一个CHL的FIFO队列。下面是Node节点的属性。
volatile int waitStatus; 节点的等待状态,一个节点可能位于以下几种状态:
- CANCELLED = 1: 节点操作因为超时或者对应的线程被interrupt。节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出。
- SIGNAL = -1: 节点的继任节点是(或者将要成为)BLOCKED状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的继任节点。
- CONDITION = -2:表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。
- 0: 正常状态,新生的非CONDITION节点都是此状态。
- 非负值标识节点不需要被通知(唤醒)。
volatile Node prev;此节点的前一个节点。节点的waitStatus依赖于前一个节点的状态。
volatile Node next;此节点的后一个节点。后一个节点是否被唤醒(uppark())依赖于当前节点是否被释放。
volatile Thread thread;节点绑定的线程。
Node nextWaiter;下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。
AQS 在J.U.C里面是一个非常核心的工具,而且也非常复杂,里面考虑到了非常多的逻辑实现,所以在后面的章节中总是不断的尝试介绍AQS的特性和实现。
这一个小节主要介绍了一些理论背景和相关的数据结构,在下一个小节中将根据以上知识来了解Lock.lock/unlock是如何实现的。
参考资料:
(1)ReentrantLock代码剖析之ReentrantLock.lock ReentrantLock代码剖析之ReentrantLock.unlock ReentrantLock代码剖析之ReentrantLock.lockInterruptibly
(2)java多线程--java.util.concurrent.locks.AbstractQueuedSynchronizer解析(只包含多线程同步示例)
(4)AbstractQueuedSynchronizer源码解析之ReentrantLock(一) AbstractQueuedSynchronizer源码解析之ReentrantLock(二)
(5)The java.util.concurrent Synchronizer Framework
深入浅出 Java Concurrency (7): 锁机制 part 2 AQS[转]的更多相关文章
- 深入浅出 Java Concurrency (7): 锁机制 part 2 AQS
在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复杂的一个基础类:java.util.concurrent.locks.AbstractQueuedSynchronizer ...
- 深入浅出 Java Concurrency (6): 锁机制 part 1 Lock与ReentrantLock
前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最后的总结篇来整体说明.从这一章开始花少量的篇幅谈谈锁机制. 上一个章节中谈到了锁机制,并且针对于原子操作谈了一些相关的概念 ...
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题
主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的 ...
- 深入浅出 Java Concurrency (9): 锁机制 part 4 锁释放与条件变量 (Lock.unlock And Condition)
本小节介绍锁释放Lock.unlock(). Release/TryRelease unlock操作实际上就调用了AQS的release操作,释放持有的锁. public final boolean ...
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题[转]
主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的一点 ...
- 深入浅出 Java Concurrency (9): 锁机制 part 4[转]
本小节介绍锁释放Lock.unlock(). Release/TryRelease unlock操作实际上就调用了AQS的release操作,释放持有的锁. public final boolean ...
- 深入浅出 Java Concurrency (6): 锁机制 part 1[转]
前面的章节主要谈谈原子操作,至于与原子操作一些相关的问题或者说陷阱就放到最后的总结篇来整体说明.从这一章开始花少量的篇幅谈谈锁机制. 上一个章节中谈到了锁机制,并且针对于原子操作谈了一些相关的概念和设 ...
- 深入浅出 Java Concurrency (12): 锁机制 part 7 信号量(Semaphore)
Semaphore 是一个计数信号量.从概念上讲,信号量维护了一个许可集.如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可.每个 release() 添加一个许可,从而可能 ...
- 深入浅出 Java Concurrency (12): 锁机制 part 7 信号量(Semaphore)[转]
Semaphore 是一个计数信号量.从概念上讲,信号量维护了一个许可集.如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可.每个 release() 添加一个许可,从而可能释放 ...
随机推荐
- 使用C++网络库libcurl
libcurl使用问题记录 libcurl库的初始化和关闭 curl_global_init()和curl_global_cleanup().这两个函数并不是线程安全的.所以只能在主线程中进行一次的初 ...
- Han Xin and His Troops(扩展中国剩余定理 Python版)
Han Xin and His Troops(扩展中国剩余定理 Python版) 题目来源:2019牛客暑期多校训练营(第十场) D - Han Xin and His Troops 题意: 看标 ...
- <day004>小娜显示空白+CSV文件的基本操作+普通的代理使用
小知识: 当小娜搜索显示空白的时候,怎么解决? 任务管理器结束小娜进程就好了= =*(多半是惯得,关掉就好了!) 任务1:CSV文件的基本操作 import csv import pandas as ...
- <爬虫>黑板爬虫闯关02
import requests from lxml import etree ''' 黑板爬虫闯关02 网址:http://www.heibanke.com/lesson/crawler_ex01/ ...
- 面试系列20 生产环境中的redis是怎么部署的
redis cluster,10台机器,5台机器部署了redis主实例,另外5台机器部署了redis的从实例,每个主实例挂了一个从实例,5个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每 ...
- java_Properties集合
package propertiesTest; import java.io.FileReader; import java.io.FileWriter; import java.io.IOExcep ...
- 如何在Vue项目中使用Element组件
[前提] 1.安装webpack cnpm install webpack -g 2.安装vue/vue-cli cnpm install vue vue-cli -g 3.初始化vue ...
- vue 简单留言本
代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- Jmeter性能测试 入门【转】
Jmeter性能测试 入门[转] Jmeter是一款优秀的开源测试工具, 是每个资深测试工程师,必须掌握的测试工具,熟练使用Jmeter能大大提高工作效率. 熟练使用Jmeter后, 能用Jmeter ...
- SQL数据表纵横转换
SELECT DISTINCT '(select b.risk from rhwl_easy_genes_new_risk b where b.genes_id=a.id and b.disease= ...