Java 并发编程-再谈 AbstractQueuedSynchronizer 3 :基于 AbstractQueuedSynchronizer 的并发类实现
公平模式ReentrantLock实现原理
前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQueuedSynchronizer的并发类是如何实现的。
ReentrantLock显然是一种独占锁,首先是公平模式的ReentrantLock,Sync是ReentractLock中的基础类,继承自AbstractQueuedSynchronizer,看一下代码实现:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | abstractstaticclassSync extendsAbstractQueuedSynchronizer {    privatestaticfinallongserialVersionUID = -5179523762034025860L;    /**     * Performs {@link Lock#lock}. The main reason for subclassing     * is to allow fast path for nonfair version.     */    abstractvoidlock();    /**     * Performs non-fair tryLock.  tryAcquire is     * implemented in subclasses, but both need nonfair     * try for trylock method.     */    finalbooleannonfairTryAcquire(intacquires) {        finalThread current = Thread.currentThread();        intc = getState();        if(c == 0) {            if(compareAndSetState(0, acquires)) {                setExclusiveOwnerThread(current);                returntrue;            }        }        elseif(current == getExclusiveOwnerThread()) {            intnextc = c + acquires;            if(nextc < 0) // overflow                thrownewError("Maximum lock count exceeded");            setState(nextc);            returntrue;        }        returnfalse;    }    protectedfinalbooleantryRelease(intreleases) {        intc = getState() - releases;        if(Thread.currentThread() != getExclusiveOwnerThread())            thrownewIllegalMonitorStateException();        booleanfree = false;        if(c == 0) {            free = true;            setExclusiveOwnerThread(null);        }        setState(c);        returnfree;    }    protectedfinalbooleanisHeldExclusively() {        // While we must in general read state before owner,        // we don't need to do so to check if current thread is owner        returngetExclusiveOwnerThread() == Thread.currentThread();    }    finalConditionObject newCondition() {        returnnewConditionObject();    }    // Methods relayed from outer class    finalThread getOwner() {        returngetState() == 0? null: getExclusiveOwnerThread();    }    finalintgetHoldCount() {        returnisHeldExclusively() ? getState() : 0;    }    finalbooleanisLocked() {        returngetState() != 0;    }    /**     * Reconstitutes this lock instance from a stream.     * @param s the stream     */    privatevoidreadObject(java.io.ObjectInputStream s)        throwsjava.io.IOException, ClassNotFoundException {        s.defaultReadObject();        setState(0); // reset to unlocked state    }} | 
Sync属于一个公共类,它是抽象的说明Sync会被继承,简单整理一下Sync主要做了哪些事(因为Sync不是ReentrantLock公平锁的关键):
- 定义了一个lock方法让子类去实现,我们平时之所以能调用ReentrantLock的lock()方法,就是因为Sync定义了它
- 实现了非公平锁tryAcquira的方法
- 实现了tryRelease方法,比较简单,状态-1,独占锁的线程置空
- 实现了isHeldExclusively方法
- 定义了newCondition方法,让开发者可以利用Condition实现通知/等待
接着,看一下公平锁的实现,FairSync类,它继承自Sync:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | staticfinalclassFairSync extendsSync {    privatestaticfinallongserialVersionUID = -3000897897090466540L;    finalvoidlock() {        acquire(1);    }    /**     * Fair version of tryAcquire.  Don't grant access unless     * recursive call or no waiters or is first.     */    protectedfinalbooleantryAcquire(intacquires) {        finalThread current = Thread.currentThread();        intc = getState();        if(c == 0) {            if(!hasQueuedPredecessors() &&                compareAndSetState(0, acquires)) {                setExclusiveOwnerThread(current);                returntrue;            }        }        elseif(current == getExclusiveOwnerThread()) {            intnextc = c + acquires;            if(nextc < 0)                thrownewError("Maximum lock count exceeded");            setState(nextc);            returntrue;        }        returnfalse;    }} | 
整理一下要点:
1. 每次acquire的时候,state+1,如果当前线程lock()之后又lock()了,state不断+1,相应的unlock()的时候state-1,直到将state减到0为之,说明当前线程释放完所有的状态,其它线程可以竞争
2. state=0的时候,通过hasQueuedPredecessors方法做一次判断,hasQueuedPredecessors的实现为”h != t && ((s = h.next) == null || s.thread != Thread.currentThread());”,其中h是head、t是tail,由于代码中对结果取反,因此取反之后的判断为”h == t || ((s = h.next) != null && s.thread == Thread.currentThread());”,总结起来有两种情况可以通过!hasQueuedPredecessors()这个判断:
- h==t,h==t的情况为要么当前FIFO队列中没有任何数据,要么只构建出了一个head还没往后面连过任何一个Node,因此head就是tail
- (s = h.next) != null && s.thread == Thread.currentThread(),当前线程为正在等待的第一个Node中的线程
3. 如果没有线程比当前线程等待更久去执行acquire操作,那么通过CAS操作将state从0变为1的线程tryAcquire成功
4. 没有tryAcquire成功的线程,按照tryAcquire的先后顺序,构建为一个FIFO队列,即第一个tryAcquire失败的排在head的后一位,第二个tryAcquire失败的排在head的后二位
5. 当tryAcquire成功的线程release完毕,第一个tryAcquire失败的线程第一个尝试tryAcquire,这就是先到先得,典型的公平锁
非公平模式ReentrantLock实现原理
看完了公平模式ReentrantLock,接着我们看一下非公平模式ReentrantLock是如何实现的。NonfairSync类,同样是继承自Sync类,实现为:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | staticfinalclassNonfairSync extendsSync {    privatestaticfinallongserialVersionUID = 7316153563782823691L;    /**     * Performs lock.  Try immediate barge, backing up to normal     * acquire on failure.     */    finalvoidlock() {        if(compareAndSetState(0, 1))            setExclusiveOwnerThread(Thread.currentThread());        else            acquire(1);    }    protectedfinalbooleantryAcquire(intacquires) {        returnnonfairTryAcquire(acquires);    }} | 
结合nonfairTryAcquire方法一起讲解,nonfairTryAcquire方法的实现为:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | finalbooleannonfairTryAcquire(intacquires) {    finalThread current = Thread.currentThread();    intc = getState();    if(c == 0) {        if(compareAndSetState(0, acquires)) {            setExclusiveOwnerThread(current);            returntrue;        }    }    elseif(current == getExclusiveOwnerThread()) {        intnextc = c + acquires;        if(nextc < 0) // overflow            thrownewError("Maximum lock count exceeded");        setState(nextc);        returntrue;    }    returnfalse;} | 
看到差别就在于非公平锁lock()的时候会先尝试通过CAS看看能不能把state从0变为1(即获取锁),如果可以的话,直接获取锁而不需要排队。举个实际例子就很好理解了:
- 线程1、线程2、线程3竞争锁,线程1竞争成功获取锁,线程2、线程3依次排队
- 线程1执行完毕,释放锁,state变为0,唤醒了第一个排队的线程2
- 此时线程4来尝试获取锁了,由于线程2被唤醒了,因此线程2与线程4竞争锁
- 线程4成功将state从0变为1,线程2竞争锁失败,继续park
看到整个过程中,后来的线程4反而比先来的线程2先获取锁,相当于是一种非公平的模式,
那为什么非公平锁效率会比公平锁效率高?上面第(3)步如果线程2和线程4不竞争锁就是答案。为什么这么说,后面的解释很重要,希望大家可以理解:
线程1是先将state设为0,再去唤醒线程2,这两个过程之间是有时间差的。
那么如果线程1将state设置为0的时候,线程4就通过CAS算法获取到了锁,且在线程1唤醒线程2之前就已经使用完毕锁,那么相当于线程2获取锁的时间并没有推迟,在线程1将state设置为0到线程1唤醒线程2的这段时间里,反而有线程4获取了锁执行了任务,这就增加了系统的吞吐量,相当于单位时间处理了更多的任务。
从这段解释我们也应该能看出来了,非公平锁比较适合加锁时间比较短的任务。这是因为加锁时间长,相当于线程2将state设为0并去唤醒线程2的这段时间,线程4无法完成释放锁,那么线程2被唤醒由于没法获取到锁,又被阻塞了,这种唤醒-阻塞的操作会引起线程的上下文切换,继而影响系统的性能。
Semaphore实现原理
Semaphore即信号量,用于控制代码块的并发数,将Semaphore的permits设置为1相当于就是synchronized或者ReentrantLock,Semaphore具体用法可见Java多线程19:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger。信号量允许多条线程获取锁,显然它的锁是一种共享锁,信号量也有公平模式与非公平模式,相信看懂了上面ReentrantLock的公平模式与非公平模式的朋友应该对Semaphore的公平模式与非公平模式理解起来会更快,这里就放在一起写了。
首先还是看一下Semaphore的基础设施,它和ReentrantLock一样,也有一个Sync:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | abstractstaticclassSync extendsAbstractQueuedSynchronizer {    privatestaticfinallongserialVersionUID = 1192457210091910933L;    Sync(intpermits) {        setState(permits);    }    finalintgetPermits() {        returngetState();    }    finalintnonfairTryAcquireShared(intacquires) {        for(;;) {            intavailable = getState();            intremaining = available - acquires;            if(remaining < 0||                compareAndSetState(available, remaining))                returnremaining;        }    }    protectedfinalbooleantryReleaseShared(intreleases) {        for(;;) {            intcurrent = getState();            intnext = current + releases;            if(next < current) // overflow                thrownewError("Maximum permit count exceeded");            if(compareAndSetState(current, next))                returntrue;        }    }    finalvoidreducePermits(intreductions) {        for(;;) {            intcurrent = getState();            intnext = current - reductions;            if(next > current) // underflow                thrownewError("Permit count underflow");            if(compareAndSetState(current, next))                return;        }    }    finalintdrainPermits() {        for(;;) {            intcurrent = getState();            if(current == 0|| compareAndSetState(current, 0))                returncurrent;        }    }} | 
和ReentrantLock的Sync差不多,Semaphore的Sync定义了以下的一些主要内容:
- getPermits方法获取当前的许可剩余量还剩多少,即还有多少线程可以同时获得信号量
- 定义了非公平信号量获取共享锁的逻辑nonfairTryAcquireShared
- 定义了公平模式释放信号量的逻辑tryReleaseShared,相当于释放一次信号量,state就向上+1(信号量每次的获取与释放都是以1为单位的)
再看下公平信号量的实现,同样的FairSync,继承自Sync,代码为:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | staticfinalclassFairSync extendsSync {    privatestaticfinallongserialVersionUID = 2014338818796000944L;    FairSync(intpermits) {        super(permits);    }    protectedinttryAcquireShared(intacquires) {        for(;;) {            if(hasQueuedPredecessors())                return-1;            intavailable = getState();            intremaining = available - acquires;            if(remaining < 0||                compareAndSetState(available, remaining))                returnremaining;        }    }} | 
首先第10行的hasQueuedPredecessors方法,前面已经说过了,如果已经有了FIFO队列或者当前线程不是FIFO队列中在等待的第一条线程,返回-1,表示无法获取共享锁成功。
接着获取available,available就是state,用volatile修饰,所以线程中可以看到最新的state,信号量的acquires是1,每次获取信号量都对state-1,两种情况直接返回:
- remaining减完<0
- 通过cas设置成功
之后就是和之前说过的共享锁的逻辑了,如果返回的是一个<0的数字,那么构建FIFO队列,线程阻塞,直到前面的执行完才能唤醒后面的。
接着看一下非公平信号量的实现,NonfairSync继承Sync:
| 1 2 3 4 5 6 7 8 9 10 11 | staticfinalclassNonfairSync extendsSync {    privatestaticfinallongserialVersionUID = -2694183684443567898L;    NonfairSync(intpermits) {        super(permits);    }    protectedinttryAcquireShared(intacquires) {        returnnonfairTryAcquireShared(acquires);    }} | 
nonfairTryAcquireShared在父类已经实现了,再贴一下代码:
| 1 2 3 4 5 6 7 8 9 | finalintnonfairTryAcquireShared(intacquires) {    for(;;) {        intavailable = getState();        intremaining = available - acquires;        if(remaining < 0||            compareAndSetState(available, remaining))            returnremaining;    }} | 
看到这里和公平Semaphore只有一点差别:不会前置进行一次hasQueuedPredecessors()判断。即当前有没有构建为一个FIFO队列,队列里面第一个等待的线程是不是自身都无所谓,对于非公平Semaphore都一样,反正线程调用Semaphore的acquire方法就将当前state-1,如果得到的remaining设置成功或者CAS操作成功就返回,这种操作没有遵循先到先得的原则,即非公平信号量。
至于非公平信号量对比公平信号量的优点,和ReentrantLock的非公平锁对比ReentrantLock的公平锁一样,就不说了。
CountDownLatch实现原理
CountDownLatch即计数器自减的一种闭锁,某线程阻塞,对一个计数器自减到0,此线程被唤醒,CountDownLatch具体用法可见Java多线程19:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger。
CountDownLatch是一种共享锁,通过await()方法与countDown()两个方法实现自身的功能,首先看一下await()方法的实现:
| 1 2 3 | publicvoidawait() throwsInterruptedException {      sync.acquireSharedInterruptibly(1); } | 
acquireSharedInterruptibly最终又回到tryAcquireShared方法上,直接贴整个Sync的代码实现:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | privatestaticfinalclassSync extendsAbstractQueuedSynchronizer {    privatestaticfinallongserialVersionUID = 4982264981922014374L;    Sync(intcount) {        setState(count);    }    intgetCount() {        returngetState();    }    protectedinttryAcquireShared(intacquires) {        return(getState() == 0) ? 1: -1;    }    protectedbooleantryReleaseShared(intreleases) {        // Decrement count; signal when transition to zero        for(;;) {            intc = getState();            if(c == 0)                returnfalse;            intnextc = c-1;            if(compareAndSetState(c, nextc))                returnnextc == 0;        }    }} | 
其实看到tryAcquireShared方法,理解AbstractQueuedSynchronizer共享锁原理的,不用看countDown方法应该都能猜countDown方法是如何实现的。我这里总结一下:
- 传入一个count,state就等于count,await的时候判断是不是0,是0返回1表示成功,不是0返回-1表示失败,构建FIFO队列,head头只连接一个Node,Node中的线程就是调用CountDownLatch的await()方法的线程
- 每次countDown的时候对state-1,直到state减到0的时候才算tryReleaseShared成功,tryReleaseShared成功,唤醒被挂起的线程
为了验证(2),看一下上面Sync的tryReleaseShared方法就可以了,确实是这么实现的。
再理解独占锁与共享锁的区别
本文详细分析了ReentrantLock、Semaphore、CountDownLatch的实现原理,第一个是基于独占锁的实现,后两个是基于共享锁的实现,从这三个类我们可以再总结一下独占锁与共享锁的区别,主要在两点上:
- 独占锁同时只有一条线程可以acquire成功,独占锁同时可能有多条线程可以acquire成功,Semaphore是典型例子;
- 独占锁每次只能唤醒一个Node,共享锁每次唤醒的时候可以将状态向后传播,即可能唤醒多个Node,CountDownLatch是典型例子。
带着这两个结论再看ReentrantLock、Semaphore、CountDownLatch,你一定会对独占锁与共享锁理解更深。
Java 并发编程-再谈 AbstractQueuedSynchronizer 3 :基于 AbstractQueuedSynchronizer 的并发类实现的更多相关文章
- Java并发编程-再谈 AbstractQueuedSynchronizer 1 :独占模式
		关于AbstractQueuedSynchronizer JDK1.5之后引入了并发包java.util.concurrent,大大提高了Java程序的并发性能.关于java.util.concurr ... 
- 再谈AbstractQueuedSynchronizer3:基于AbstractQueuedSynchronizer的并发类实现
		公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ... 
- Java 并发编程-再谈 AbstractQueuedSynchronizer 2:共享模式与基于 Condition 的等待 / 通知机制实现
		共享模式acquire实现流程 上文我们讲解了AbstractQueuedSynchronizer独占模式的acquire实现流程,本文趁热打铁继续看一下AbstractQueuedSynchroni ... 
- Java继承之再谈构造器
		目录 Java继承之再谈构造器 初始化基类 默认构造器 带参数的构造器 子类调用父类构造器 Java继承之再谈构造器 初始化基类 前面提到,继承是子类对父类的拓展.<Thinking in Ja ... 
- 再谈AbstractQueuedSynchronizer:基于AbstractQueuedSynchronizer的并发类实现
		公平模式ReentrantLock实现原理 前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQue ... 
- Java并发编程原理与实战三十五:并发容器ConcurrentLinkedQueue原理与使用
		一.简介 一个基于链接节点的无界线程安全队列.此队列按照 FIFO(先进先出)原则对元素进行排序.队列的头部 是队列中时间最长的元素.队列的尾部 是队列中时间最短的元素.新的元素插入到队列的尾部,队列 ... 
- Java并发编程:什么是线程安全,以及并发必须知道的几个概念
		废话 众所周知,在Java的知识体系中,并发编程是非常重要的一环,也是面试的必问题,一个好的Java程序员是必须对并发编程这块有所了解的.为了追求成为一个好的Java程序员,我决定从今天开始死磕Jav ... 
- java并发编程系列原理篇--JDK中的通信工具类Semaphore
		前言 java多线程之间进行通信时,JDK主要提供了以下几种通信工具类.主要有Semaphore.CountDownLatch.CyclicBarrier.exchanger.Phaser这几个通讯类 ... 
- 《C#并发编程经典实例》学习笔记-第一章并发编程概述
		并发编程的术语 并发 同时做多件事情 多线程 并发的一种形式,它采用多个线程来执行程序. 多线程是并发的一种形式,但不是唯一的形式. 并行处理 把正在执行的大量的任务分割成小块,分配给多个同时运行的线 ... 
随机推荐
- jq封装
			<div id='container' class='container'>盒子</div> <button onClick="f()" >te ... 
- 解决jenkins shell执行sonar-scanner提示命令存在的问题
			通过jenkins的以下三个方式去执行sonar-scanner,抛如下错误. Send files or execute commands over SSH before the build sta ... 
- Oracle授权
			给连接权限 grant connect to 用户; 给资源权限 grant resource to 用户; 给DBA权限 grant dba to 用户; 授权语句 --select * from ... 
- mvc开发网站打开慢总结
			开始学习mvc开发网站的时候,看了传智博客的视频教程,其中学习了一个和牛逼的框架,开始激动的深入学习,学完后却发现其实那套框架太重并不适合一些中小型的网站开发,并且也使用导航属性关联外键,导致打开网站 ... 
- 学习Python第六天
			今天我们讲讲数据类型中的集合,博客写得有点糙,后续应该要进行优化优化了........ 集合:无序,不重复的数据组合,主要作用:去重,把一个列表变成集合,就自动去重了 基本语法:S = {1}类型为集 ... 
- Python序列结构--字典
			字典:反映对应关系的映射类型 字典(dict)是包含若干“键:值”元素的无序可变序列 字典中元素的“键”可以是python中任意不可变数据,例如整数.实数.复数.字符串.元组等类型可哈希数据,“键”不 ... 
- Java编程题(1):n个数里出现次数大于等于n/2的数
			题目描述:输入n个整数,输出出现次数大于等于数组长度一半的数. 输入描述:每个测试输入包含 n个空格分割的n个整数,n不超过100,其中有一个整数出现次数大于等于n/2. 输出描述:输出出现次数大于等 ... 
- 记一次生产环境thrift服务的配置问题
			问题现象 有客户反馈我们的产品有时反应很慢,处理会出现超时. 问题分析过程 1.第一反应可能是用户增加,并发量太大了,询问了运营,最近用户注册数据并没有猛增. 2.分析access日志,发现有隔一段时 ... 
- 调用接口,发送https请求
			调用https接口有两种方式 一:是导入证书; 证书调用https请求本人还不知道,希望看见此博客的大神给我回复. 二:是创建信任管理器. 第一步:实现X509TrustManager接口,里面的方法 ... 
- Docker 三剑客之 Docker Compose
			Docker Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排,开源地址:https://github.com/docker/compose Dock ... 
