AQS的使用和reentrantlock
AQS概述
AQS是一个同步器,全称是AbstractQueuedSynchronizer类。
使用方法:子类继承AQS,然后重写tryAcquire、tryRelease、isHeldExclusively(如果是共享模式实现tryAcquireShared和tryReleaseShared方法),然后将其作为内部类,外部类定义unlock和lock方法并调用acquire和release方法即可(共享模式调用acquireShared和releaseShared)。
线程协调场景主要分为两类,一个线程在进行操作时不允许其他线程操作,即独占模式,还有允许多个线程操作的情况,称为共享模式。(独占和共享两种模式可以两个同时实现)
一个关键变量:内部封装了一个volatile修饰的int类型的字段state,代表当前同步状态。
定义了访问该字段的几个方法getState()、setState(val)、compareAndSetState(expect, update)
独占模式
独占模式下保证线程同步的操作是这样的,将state设为0,当某个线程要独占时都需要先判断state是不是0,如果不是就进入阻塞状态等待,如果是0就把state设置为1。然后进行操作,实现这个操作需要设置3种操作:分别是尝试获取同步状态、释放同步状态、判断是否有线程独占。
这三种操作分别对应下面三个方法tryAcquire(tryAcquireNanos方法加了超时限制,超时就自动视为获取失败)、tryRelease、isHeldExclusively。
public class PlainLock { private static class Sync extends AbstractQueuedSynchronizer{ @Override protected boolean tryAcquire(int arg) { // TODO Auto-generated method stub return compareAndSetState(0, 1); } @Override protected boolean tryRelease(int arg) { // TODO Auto-generated method stub setState(0); return true; } @Override protected boolean isHeldExclusively() { // TODO Auto-generated method stub return getState() == 1; } } private Sync sync = new Sync(); public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } }
共享模式
当同步器使用共享模式时,区别在于允许多个线程持有资源,这个数量就是state的初始值,共享模式下需要定义两个方法tryAcquireShared(val)、tryReleaseShared(val),也是尝试获得和释放同步状态,尝试获得时返回值为int数,返回一个非负数代表获得同步状态成功(需要在aqs的构造方法中设置setState方法来确定可以同时并发的线程),然后重定义lock和unlock方法内部使用acquireShared和releaseShared方法。
//保证不超过两个线程同时访问的锁 public class DoubleLock { private class Sync extends AbstractQueuedSynchronizer{ public Sync() { super(); setState(2); } //共享模式下需要一开始在构造方法里设置state的值 //然后在重写tryAcquireShared和tryReleaseShared方法 //该返回值大于等于0说明获取同步状态成功,否则加入同步队列 @Override protected int tryAcquireShared(int arg) { // TODO Auto-generated method stub while(true) { int now = getState(); int next = getState() - arg; if(compareAndSetState(now, next)) { return next; } } } @Override protected boolean tryReleaseShared(int arg) { // TODO Auto-generated method stub while(true) { int now = getState(); int next = getState() + arg; if(compareAndSetState(now, next)) { return true; } } } } private Sync sy = new Sync(); public void lock() { sy.acquireShared(1); } public void unlock() { sy.releaseShared(1); } }
acquire和release方法
AQS内部维护了一个node类,node中有两个指针和一个线程Thread,AQS内部维护了两个node,分别是node队列的头和尾节点。
acquire(args)方法就是获取同步状态,如果成功就返回,如果失败就将本线程加入同步队列。内部首先调用tryAcquire(args)方法尝试获取同步状态,如果失败之后会调用addWaiter和acquireQueued方法封装node插入同步队列并再次尝试获取,失败后会检查队列中node的状态,修改node的状态并阻塞。
(还可以使用acquireInterruptibly方法,它是可中断的,如果没获取到而阻塞,其他线程中断了该线程就会抛出interruptedEx异常)
release(args)方法就是释放同步状态,会调用tryrelease方法,如果成功了就唤醒同步队列的下一个节点,并释放头节点。
reentrantlock的实现
Reentrantlock内部定义了一个AQS的子类,每次new一个Reentrantlock就相当于new了一个AQS,内部封装着一个同步队列,AQS内部还有一个ConditionObject的内部类,这个内部类内部维护了两个node类型变量,相当于维护了一个队列,AQS中的队列和这个ConditionObject中的队列用的是同一个node。
Reentrantlock有一个方法newCondition,这个方法会返回一个ConditionObject类,也就是说每调用newCondition方法一次,就会构建出一个队列(这个等待队列是单向链表,而aqs同步队列是双向链表)。
ConditionObject类实现了condition接口,这个接口内部有await和signal方法,await方法执行后,该线程对应的node就会被放入对应condition的队列中,从AQS同步队列中取下(当取下node后AQS同步队列为空时那么最后这一个node就不会被取下),同时改变node的等待状态,signal方法负责唤醒线程,就是将对应condition中的node取下再放回AQS同步队列中去,同时改变node的等待状态。
Reentrantlock这种可以创建多个condition,也就是创建了多个等待队列,可以实现特定的唤醒,使一个显式锁对应多个等待队列的效果。
来源:https://www.cnblogs.com/shizhuoping/p/11532096.html