学习笔记-理解锁中的同步队列AQS
概念
队列同步器AbstractQueuedSynchronizer,也就是我们经常说的AQS,他是java并发工具包里面的一个对象,我们在使用lock的实现类的时候,会经常看到他的身影。大多都是使用它来构建锁或者其他同步组件的基础框架,他使用一个volatile修改的int成员变量来表示同步状态,通过内置一个Node对象来完成资源获取线程的排队工作,这个Node其本质也就是一个FIFO(先进先出)队列。AQS的主要使用方式是通过继承,子类继承AQS,然后实现它的抽象方法来管理同步状态,在实现抽象方法的过程中都是避免不了要对同步状态进行修改的,这个时候就需要使用同步器提供的3个方法:getState(),setState(int newState),compareAndSetState(int expect,int update) 来进行操作,因为这个三个方法能够保证状态的修改是安全的。并且我的这个子类通常都是被定义在同步组件的内部静态类的方法方式来使用,而同步器它自己是没有实现任何同步接口的,他仅仅是定义了几个同步状态的获取和释放来提供给同步组件使用。
同步器的接口与实现
AQS的设计是其实基于模板方法模式来的,使用者需要继承AQS并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,来调用AQS提供的模板方法,而这些方法将会调用使用者重写的方法。重写同步器指定的方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态。1,getState():获取当前同步状态。2,setState(int newState):设置当前同步状态。3,compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性。下面是自己写的一个简单同步器,也是顺便理解一下使用方式。
1 import java.util.concurrent.TimeUnit;
2 import java.util.concurrent.locks.AbstractQueuedSynchronizer;
3 import java.util.concurrent.locks.Condition;
4 import java.util.concurrent.locks.Lock;
5 /**
6 * @author yk
7 * @version 1.0
8 * @describe SimpleMutex
9 * @date 2017-05-22 12:00
10 */
11 public class SimpleMutex implements Lock {
12 //静态内部类,自定义同步器
13 private static class Sync extends AbstractQueuedSynchronizer{
14 //是否处于占用状态
15 protected boolean isHeldExclusively()
16 {
17 return getState()==1;
18 }
19 // 当状态为0的时候获取锁
20 public boolean tryAcquire(int acquires)
21 {
22 if(compareAndSetState(0,1))
23 {
24 setExclusiveOwnerThread(Thread.currentThread());
25 return true;
26 }
27 return false;
28 }
29 //释放锁将状态修改为0
30 protected boolean tryRelease(int releases) {
31 if (getState() == 0) {
32 throw new IllegalMonitorStateException();
33 }
34 setExclusiveOwnerThread(null);
35 setState(0);
36 return true;
37 }
38 // 返回一个Condition,每个condition都包含了一个condition队列
39 Condition newCondition() {
40 return new ConditionObject();
41 }
42 }
43 // 仅需要将操作代理到Sync上
44 private final Sync sync=new Sync();
45
46 @Override
47 public void lock() {
48 sync.acquire(1);
49 }
50
51 @Override
52 public void lockInterruptibly() throws InterruptedException {
53 sync.acquireInterruptibly(1);
54 }
55
56 @Override
57 public boolean tryLock() {
58 return sync.tryAcquire(1);
59 }
60
61 @Override
62 public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
63 return sync.tryAcquireSharedNanos(1,unit.toNanos(time));
64 }
65
66 @Override
67 public void unlock() {
68 sync.tryRelease(0);
69 }
70
71 @Override
72 public Condition newCondition() {
73 return sync.newCondition();
74 }
75
76 public boolean isLocked()
77 {
78 return sync.isHeldExclusively();
79 }
80
81 public boolean hasQueuedThreads()
82 {
83 return sync.hasQueuedThreads();
84 }
85 }
1 static final class Node {
2 //表示线程以共享的模式等待锁
3 static final Node SHARED = new Node();
4 //表示线程正在以独占的方式等待锁
5 static final Node EXCLUSIVE = null;
6
7 //当前节点由于超时或中断被取消
8 static final int CANCELLED = 1;
9 //表示当前节点的前节点被阻塞
10 static final int SIGNAL = -1;
11 //当前节点在等待condition
12 static final int CONDITION = -2;
13 //状态需要向后传播
14 static final int PROPAGATE = -3;
15
16 /**
17 * 当前节点在队列中的状态,他有五个枚举值:
18 * 0 当一个Node被初始化的时候的默认值
19 * CANCELLED 为1,表示线程获取锁的请求已经取消了
20 * CONDITION 为-2,表示节点在等待队列中,节点线程等待唤醒
21 * PROPAGATE 为-3,当前线程处在SHARED情况下,该字段才会使用
22 * SIGNAL 为-1,表示线程已经准备好了,就等资源释放了
23 */
24 volatile int waitStatus;
25 //前驱节点
26 volatile Node prev;
27 //后继节点
28 volatile Node next;
29 //表示处于该节点的线程
30 volatile Thread thread;
31 //指向下一个处于CONDITION状态的节点
32 Node nextWaiter;
33
34 final boolean isShared() {
35 return nextWaiter == SHARED;
36 }
37 //返回前驱节点,没有的话抛出npe
38 final Node predecessor() throws NullPointerException {
39 Node p = prev;
40 if (p == null)
41 throw new NullPointerException();
42 else
43 return p;
44 }
45
46 Node() {
47 }
48
49 Node(Thread thread, Node mode) {
50 this.nextWaiter = mode;
51 this.thread = thread;
52 }
53
54 Node(Thread thread, int waitStatus) {
55 this.waitStatus = waitStatus;
56 this.thread = thread;
57 }
58 }
1 public final void acquire(int arg) {
2 if (!tryAcquire(arg) &&
3 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
4 selfInterrupt();
5 }
这个代码主要是为了完成同步状态的获取,节点构造,加入同步队列以及在同步队列中自旋。其中主要逻辑:首先调用自定义同步器实现的tryAcquire方法,这个方法保证了线程安全的获取同步状态,如果同步状态获取失败,则构造一个节点,并且通过addWaiter把这个节点加入到同步队列的尾部。最后调用acquireQueued,使这个节点以死循环的方式获取同步状态。如果获取不到则阻塞节点中的线程,而这些被阻塞的线程的唤醒主要依靠前驱节点的出队或阻塞线程的中断来实现。
接下来我们看下同步器的addWaiter和enq方法。
1 private Node addWaiter(Node mode) {
2 Node node = new Node(Thread.currentThread(), mode);
3 // 快速尝试在尾部添加
4 Node pred = tail;
5 if (pred != null) {
6 node.prev = pred;
7 if (compareAndSetTail(pred, node)) {
8 pred.next = node;
9 return node;
10 }
11 }
12 enq(node);
13 return node;
14 }
15
16 private Node enq(final Node node) {
17 for (;;) {
18 Node t = tail;
19 if (t == null) { // 初始化队列
20 if (compareAndSetHead(new Node()))
21 tail = head;
22 } else {
23 // t是尾节点,把为节点设置为当前节点的前驱节点,然后在下一步用CAS把当前节点设置成尾节点,设置成功之后,再把当之前的尾节点的下一个节点设置成当前节点,这样,当前节点就标称了尾节点
24 node.prev = t;
25 if (compareAndSetTail(t, node)) {
26 t.next = node;
27 return t;
28 }
29 }
30 }
31 }
1 final boolean acquireQueued(final Node node, int arg) {
2 boolean failed = true;
3 try {
4 boolean interrupted = false;
5 for (;;) {
6 final Node p = node.predecessor();
7 if (p == head && tryAcquire(arg)) {
8 setHead(node);
9 p.next = null; // help GC
10 failed = false;
11 return interrupted;
12 }
13 if (shouldParkAfterFailedAcquire(p, node) &&
14 parkAndCheckInterrupt())
15 interrupted = true;
16 }
17 } finally {
18 if (failed)
19 cancelAcquire(node);
20 }
21 }
1 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
2 int ws = pred.waitStatus;
3 if (ws == Node.SIGNAL)
4 return true;
5 if (ws > 0) {
6 do {
7 node.prev = pred = pred.prev;
8 } while (pred.waitStatus > 0);
9 pred.next = node;
10 } else {
11 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
12 }
13 return false;
14 }
在acquireQueued(final Node node,int arg)方法中,当前线程在“死循环”中尝试获取同步状态,而只有前驱节点是头节点才能够尝试获取同步状态,这是为什么?原因有两个,第一,头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态之后,将会
1 public final boolean release(int arg) {
2 if (tryRelease(arg)) {
3 Node h = head;
4 if (h != null && h.waitStatus != 0)
5 unparkSuccessor(h);
6 return true;
7 }
8 return false;
9 }