Java 并发编程-再谈 AbstractQueuedSynchronizer 3 :基于 AbstractQueuedSynchronizer 的并发类实现
2017-08-29 15:40
537 查看
公平模式ReentrantLock实现原理
前面的文章研究了AbstractQueuedSynchronizer的独占锁和共享锁,有了前两篇文章的基础,就可以乘胜追击,看一下基于AbstractQueuedSynchronizer的并发类是如何实现的。ReentrantLock显然是一种独占锁,首先是公平模式的ReentrantLock,Sync是ReentractLock中的基础类,继承自AbstractQueuedSynchronizer,看一下代码实现:
定义了一个lock方法让子类去实现,我们平时之所以能调用ReentrantLock的lock()方法,就是因为Sync定义了它
实现了非公平锁tryAcquira的方法
实现了tryRelease方法,比较简单,状态-1,独占锁的线程置空
实现了isHeldExclusively方法
定义了newCondition方法,让开发者可以利用Condition实现通知/等待
接着,看一下公平锁的实现,FairSync类,它继承自Sync:
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竞争锁,线程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:
getPermits方法获取当前的许可剩余量还剩多少,即还有多少线程可以同时获得信号量
定义了非公平信号量获取共享锁的逻辑nonfairTryAcquireShared
定义了公平模式释放信号量的逻辑tryReleaseShared,相当于释放一次信号量,state就向上+1(信号量每次的获取与释放都是以1为单位的)
再看下公平信号量的实现,同样的FairSync,继承自Sync,代码为:
接着获取available,available就是state,用volatile修饰,所以线程中可以看到最新的state,信号量的acquires是1,每次获取信号量都对state-1,两种情况直接返回:
remaining减完<0
通过cas设置成功
之后就是和之前说过的共享锁的逻辑了,如果返回的是一个<0的数字,那么构建FIFO队列,线程阻塞,直到前面的执行完才能唤醒后面的。
接着看一下非公平信号量的实现,NonfairSync继承Sync:
至于非公平信号量对比公平信号量的优点,和ReentrantLock的非公平锁对比ReentrantLock的公平锁一样,就不说了。
CountDownLatch实现原理
CountDownLatch即计数器自减的一种闭锁,某线程阻塞,对一个计数器自减到0,此线程被唤醒,CountDownLatch具体用法可见Java多线程19:多线程下的其他组件之CountDownLatch、Semaphore、Exchanger。CountDownLatch是一种共享锁,通过await()方法与countDown()两个方法实现自身的功能,首先看一下await()方法的实现:
传入一个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 2:共享模式与基于 Condition 的等待 / 通知机制实现
- java基于AbstractQueuedSynchronizer实现资源共享锁,限制并发线程数目
- 【Java】关于基于并发类AbstractQueuedSynchronizer的实现研究
- 深入学习java并发编程:Lock与AbstractQueuedSynchronizer(AQS)实现
- 再谈AbstractQueuedSynchronizer3:基于AbstractQueuedSynchronizer的并发类实现
- java并发编程--AbstractQueuedSynchronizer公平锁和非公平锁分析(三)
- java并发编程--AbstractQueuedSynchronizer的tryLock()方法分析(六)
- java并发编程--AbstractQueuedSynchronizer的lock()和lockInterruptibly()方法分析(五)
- java并发编程之AbstractQueuedSynchronizer
- 基于 AbstractQueuedSynchronizer 的并发类实现
- java并发编程--AbstractQueuedSynchronizer加锁和解锁分析
- Java并发编程-AbstractQueuedSynchronizer源码分析
- 再谈AbstractQueuedSynchronizer3:基于AbstractQueuedSynchronizer的并发类实现
- Java并发编程:AbstractQueuedSynchronizer的内部结构
- 深度解析Java8 – AbstractQueuedSynchronizer的实现分析(下)
- Java显式锁学习总结之三:AbstractQueuedSynchronizer的实现原理
- 深度解析Java8 – AbstractQueuedSynchronizer的实现分析(上)
- java并发编程--AbstractQueuedSynchronizer条件锁分析(四)
- 深度解析Java8 – AbstractQueuedSynchronizer的实现分析(下)
- java并发:AbstractQueuedSynchronizer的介绍和原理分析