(六)J.U.C之locks框架:AQS独占功能剖析(二)

https://segmentfault.com/a/1190000015804888

 

一、简介

本章以RenntrantLock的调用为例,说明AbstractQueuedSynchronizer提供的独占功能。

本章结构如下:

1、以ReentrantLock的公平策略为例,分析AQS的独占功能。

2、以ReentrantLock的非公平策略为例,分析AQS的独占功能

3、分析AQS的锁中断、限时等待功能。

 

二、ReentrantLock的公平策略原理

本节对ReentrantLock公平策略的分析基于以下示例:

假设现在有3个线程:ThreadA、ThreadB、ThreadC,一个公平的独占锁,3个线程会依次尝试去获取锁:

ReentrantLock lock = new ReentrantLock(true);

线程的操作时序如下:

//ThreadA    lock

//ThreadB    lock

//ThreadC    lock

//ThreadA    release

//ThreadB    release

//ThreadC    release

 

2.1、ThreadA首先获取到锁

ThreadA首先调用ReentrantLock的lock方法,我们看下该方法的内部:

最终其实调用了FairSync的lock方法:

acquire方法来自AQS:

其中tryAcquire方法需要AQS的子类自己去实现,我们来看下ReentrantLock中的实现:

 

可以看到,在ReentrantLock中,同步状态State的含义如下:

State资源的定义
0表示锁可用
1表示锁被占用
大于1表示锁被占用,且值表示同一线程的重入次数

ThreadA是首个获取锁的线程,所以上述方法会返回true,第一阶段结束。(ThreadA一直保持占用锁的状态)

此时AQS中的等待队列还是空:

 

 

2.2ThreadB开始获取锁

终于,ThreadB要登场了,一样,ThreadB先去调用lock方法,最终调用了AQS的acquire方法:

tryAcquire方法肯定是返回false(因为此时ThreadA占有着锁)。

接下来看下addWaiter方法,这个方法其实就是将当前调用线程包装成一个【独占结点】,添加到等待队列尾部。

这里关键是enq方法,因为并发插入的情况存在,所以该方法设计成了自旋操作,保证结点能成功插入,具体步骤如下:

①当队列为空时,先创建一个dummy头结点;

②进入下一次循环,插入队尾结点;

好了,ThreadB已经被包装成结点插入队尾了,接下来会调用acquireQueued方法,这也是AQS中最重要的方法之一:

clipboard.png

在AQS中,等待队列中的线程都是阻塞的,当某个线程被唤醒时,只有该线程是首结点(线程)时,才有权去尝试获取锁。

 

上述方法中,将ThreadB包装成结点插入队尾后,先判断ThreadB是否是首结点(注意不是头结点,头结点是个dummy结点),发现确实是首节点(node.prodecessor==head),于是调用tryAcquire尝试获取锁,但是获取失败了(此时ThreadA占有着锁),就要判断是否需要阻塞当前线程。

clipboard.png

注意,对于独占功能,只使用了3种结点状态:

CANCELLED1取消。表示后驱结点被中断或超时,需要移出队列
SIGNAL-1发信号。表示后驱结点被阻塞了(当前结点在入队后、阻塞前,应确保将其prev结点类型改为SIGNAL,以便prev结点取消或释放时将当前结点唤醒。)
CONDITION-2Condition专用。表示当前结点在Condition队列中,因为等待某个条件而被阻塞了

 对于在等待队列中的线程,如果要阻塞它,需要确保将来有线程可以唤醒它,AQS中通过将前驱结点的状态置为SIGNAL:-1来表示将来会唤醒当前线程,当前线程可以安心的阻塞。

 

待续。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值