并发与队列的经典——AbstractQueuedSynchronizer

锁的思路

如果让你实现一个锁同步机制,你会怎么做?首先要考虑获取的竞争问题,其次如果不成功需要等待,这就需要一个等待队列,在合适的时候对等待的线程进行唤醒。这就是java并发控制最基类——AbstractQueuedSynchronizer所做的事情,具体的实现如下:
1、通过cas来争抢执行下去的权限,
2、在失败后将当前线程作为节点加入到等待队列中,然后通过LockSupport.park方法来进入等待,
3、直到其他获得锁的线程通过队列拿到该节点将其LockSupport.unpack。

基本流程如下:
这里写图片描述
其中重试获取锁的的流程如下:
这里写图片描述
关键点获取锁时的CAS,而后是通过LockSupport来进入与离开等待状态。而针对性地离开等待状态是依靠队列实现的。这是一个元素为保存了线程以及状态的节点的队列。

队列的维护

AQS(AbstractQueuedSynchronzer)中对该队列的维护以及及时进行线程通知方面做得非常细致周密。操作队列里元素的时候都会通过cas操作来(如addWaiter方法)。
这里写图片描述
在真正执行park前会将当前节点前已取消移除(shouldParkAfterFailedAcquire),如果真正发生了移除,又会进行一次尝试获取锁,并且顺便删除掉那些已取消的节点。
这里写图片描述
这里写图片描述
公平锁与非公平锁的区别就是公平锁在获取锁前会判断是否有正在等待的节点,而非公平锁会在未被获取锁的情况下直接去抢占锁。

这里写图片描述

拓展

lock里还有一个Condition的概念,表示获得锁的条件,它本质上也是一个锁,只是方法上类似于Object的wait和sign类型,通知的时候可以通知所有或者通知某一个等待的线程。ConditionObject这里维护了firstWaiter和lastWaiter这两个概念,分别表示第一个在等待的节点,和最后一个处于等待状态的节点,每次当前资源占有者signal的时候会将firstWaiter后移,然后将原有的firstWaiter放到队列尾部,最后通知该节点对应的线程。

附:csdn markdown 画流程图的方法:
http://www.cnblogs.com/cynchanpin/p/7196553.html

猜你喜欢

转载自blog.csdn.net/guzhangyu12345/article/details/80332573