1、AQS--锁的底层支持
1.AQS是什么
-
AQS是AbstractQueuedSychronizer的简称,即抽象同步队列的简称,这是实现同步器的重要组件,是一个抽象类,虽然在实际工做中很烧用到它,可是了解它的内部原理是颇有必要的,并法包中锁的底层就是使用该抽象类实现的,下面类图
2.分析AQS类
-
AQS是一个双向队列,head和tail变量类型是Node类型,分别用于表示队列的队首和队尾
-
在AQS中维护一个单一的状态信息state,能够经过getState,setState、compareAndSetState函数来修改其值。
-
对于ReentrantLock的实现来讲,state能够用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来讲,state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的程序可重入次数;对于semaphore来讲,state用来表示当前可用信号的个数;对于CountDownlatch来讲,state用来表示计算器当前的值。
-
对于AQS来讲,线程同步最关键的就是对state的操做,根据state是否属于同一个线程,操做state的方式分为独占式和共享方式
-
在独占方式下,获取和释放线程的方法为:void acquire(int arg) void acquireInterruptilbly(int arg) boolean release(int arg)
-
在共享方式下,获取和释放线程的方法为:void acquireShared(int arg) void acquireSharedInterruptibly(int arg) boolean releaseShared(int arg)
-
使用独占式获取资源是与具体线程绑定的,一个线程获取到了资源,就会标记这个线程获取到了,其余线程再尝试操做state获取资源时会发现当前资源不是本身持有的,就会在获取失败以后被阻塞。好比独占锁ReentrantLock的实现,在AQS内部首先会使用CAS操做把state从0变成1,而后设置当前锁的持有者是当前线程,当线程再次获取锁时发现锁的持有者就是本身,则会把状态值从1变成2,也就是设置可重入次数,而当另一个线程获取锁的时候发现本身不是锁的持有者,就会被放入AQS阻塞队列之中。
-
对应共享方式的资源是与具体线程不相关的,当多个线程使用CAS操做去竞争资源的时候,当一个线程获取到了资源,另一个资源只须要使用CAS操做获取便可。例如:Semaphore信号量,当一个线程经过acquire获取信号量的时候,会首先看当前信号量个数是否知足须要,不知足则把当前线程放入到阻塞队列中,若是知足则经过CAS操做获取信号量,会首先看当前信号量个数是否知足须要,不知足则把当前线程放入阻塞队列,若是知足就会经过自旋CAS获取信号量
3.分析Node内部类
-
变量thread是一个Thread类型,用于存放进入AQS队列的线程
-
-
SHARED用来标记该线程是获取共享资源的时候被阻塞挂起后放入AQS队列的;
-
EXCLUSIVE用来标记线程是获取独占资源时被挂起后放入AQS队列的;
-
waitStatus是用来记录线程等待状态的,能够为CANCELLED(线程被取消了),SIGNAL(线程须要被唤醒)、CONDITION(线程在条件队列里面等待)、PROPAGATE(释放资源的时候须要通知其余节点)
-
prev和next分别表明当前节点的前驱节点和后置节点。
4.AQS内部ConditionObject
-
这个类是用来实现线程同步的,ConditionObject能够直接访问AQS对象内部的变量,好比state状态值和AQS队列,ConditonObject是条件变量,每一个条件变量对应一个条件队列(单向链表列队),其用来存放调用条件变量的await方法后被阻塞的线程,如类图所示,这个条件队列的头尾元素分别为firstWaiter和lastWaiter。
5.独占方式下,获取与释放资源是如何及逆行的
-
当一个线程调用acquire(int arg)获取资源的时候,会首先使用tryAcquire
「尝试」获取资源,具体就是设置state值,成功则直接返回,失败则会当前线程封装为Node.EXCLUSIVE的Node节点,插入到AQS阻塞队列的队尾,而且调用LockSupport.park(this)方法挂起本身
public final void acquire(int arg) {
if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) {
selfInterrupt();
}
}
-
当一个线程调用release(int arg)方法的时候,会尝试使用tryRelease操做来释放资源,也就是设置state的值,而后调用LockSupport.unpark(thread)方法激活在AQS队列被阻塞的头部的一个线程。被激活的线程,而后使用tryAcquire尝试,看当前状态值state值是否知足本身的须要,若是知足,则激活线程,继续向下运行,不然仍是会被放回AQS队列中,而后被挂起
public final boolean release(int arg) {
if(tryRelease(arg)) {
Node h = head;
if(head != null && head.waitStatus != 0) {
unparkSuccessor(h);
}
return true;
}
return false;
}
-
AQS中的tryAcquire和tryRelease方法是没有提供具体实现的,须要程序员自行在子类中进行定义,它们的实现就是使用CAS算法对state进行修改,成功返回true,失败返回false,子类还须要定义当调用acquire和release方法时state状态值的增减分别表明什么含义。
-
好比继承自AQS中的独占锁ReentrantLock,定义当status为0的时候表示锁空闲,1表示该锁正在占用,重写tryAcquire方法,就是使用CAS算法,查看state是否为0,若是为0,那么置为1,而且返回true,不然,返回false;独占锁在实现release的时候,在内部使用CAS算法把当前state的值从1修改成0,而且设置当前线程的持有者为null,而后返回true,若是CAS失败,那么返回false
6.共享方式下,获取与释放资源是如何及逆行的
-
-
当一个线程调用acquireShared(int arg)获取资源的时候,会首先使用tryAcquireShared
「尝试」获取资源,具体就是设置state值,成功则直接返回,失败则会当前线程封装为Node.SHARED的Node节点,插入到AQS阻塞队列的队尾,而且调用LockSupport.park(this)方法挂起本身
public final void acquireShared(int arg) {
if(tryAcquireShared(arg) < 0) {
doAcquireShared(arg);
}
}
-
当一个线程调用releaseShared(int arg)方法的时候,会尝试使用tryRelease操做来释放资源,也就是设置state的值,而后调用LockSupport.unpark(thread)方法激活在AQS队列被阻塞的头部的一个线程。被激活的线程,而后使用tryAcquire尝试,看当前状态值state值是否知足本身的须要,若是知足,则激活线程,继续向下运行,不然仍是会被放回AQS队列中,而后被挂起
public final boolean releaseShared(int arg) {
if(tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
-
AQS类并无提供可用的tryAcquireShared和tryReleaseShared方法,正如AQS是锁阻塞和同步器的基础框架同样,tryAcquireShared和tryReleaseShared须要由具体的子类进行实现,子类在实现tryAcquiredShared和tryReleaseShared时要根据具体场景使用CAS算法尝试修改state状态变量,成功则返回true,不然返回false
-
好比继承自AQS实现的读写锁ReentrantReadWriteLock里面的读锁在重写tryAcquireShared时,首先查看写锁是否被其余线程持有,若是是则直接返回false,不然使用CAS递增的state的高16位(在ReentrantReadWriteLock中,state的高16位为获取读锁的次数)
-
好比继承自AQS实现的读写锁ReentrantReadWriteLock里面的读锁在重写tryReleaseShared时,在内部须要使用CAS算法把当前state的值高16位减1,而后返回true,若是CAS失败那么返回false
-
基于AQS实现的锁除了须要重写上述这些方法以外,还须要重写isHeldExclusively方法,来判断锁是被当前线程占用仍是被共享
7.另外对与独占方式下void acquire(int arg)和void acquireInterruptibly(int arg),与共享方式下void acquireShared(int arg)和void acquireSharedInterruptibly(int arg)之间有一个单词Interruptibly的区别是什么
-
不带interruptibly的方法意思就是不对中断进行响应,好比线程在调用了不带Interruptibly的方法获取资源或者获取资源失败被挂起的时候,其余线程中断了该线程,那么该线程不会由于被中断而抛出异常,它仍是继续获取资源或者被挂起,也就是不对中断进行响应,忽略中断
-
带有该单词的方法要对中断进行响应,也就是线程在调用了带有该单词的方法获取资源获取获取资源失败被挂起的时候,其余线程中断了该线程,该线程就会抛出InterruptException异常而返回
2、源码:
-
所在包:com.ruigege.ConcurrentListSouceCodeAnalysis5
-
https://github.com/ruigege66/ConcurrentJava
-
-
-
欢迎关注微信公众号:傅里叶变换,我的帐号,仅用于技术交流