AQS原理分析
作者:binAQS简介
AQS指的就是AbstractQueuedSynchronizer
中文直译就是:抽象队列同步器
这里我们直接摘AQS代码的注释:
* Provides a framework for implementing blocking locks and related
* synchronizers (semaphores, events, etc) that rely on
* first-in-first-out (FIFO) wait queues. This class is designed to
* be a useful basis for most kinds of synchronizers that rely on a
* single atomic {@code int} value to represent state. Subclasses
* must define the protected methods that change this state, and which
* define what that state means in terms of this object being acquired
* or released.
大概翻译一下:提供一个框架,基于FIFO(先进先出)队列的同步器,此类使用一个原子的int类型值state,并且他的子类必须定义protected方法去改变这个state,包括acquired或released
所以总结3点:
1.AQS是一个框架,ReentrantLock、Semaphore、CountDownLatch都是他的经典实现
2.他用到一个先进先出队列
3.用一个原子的int类型做状态state
4.继承他的类,要实现aquire和release方法
原理分析
我们以reentranlock的lock为入口
方法lock调用到:AbstractQueuedLongSynchronizer.acquire()
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
看代码,这里使用模版方法,AQS会调用子类的tryAcquire去获取锁,获取锁的逻辑由子类实现,如果子类获取锁失败,就会尝试入队操作。
AQS使用的队列是一个双向链表,节点的ADT就是内部类的Node,那么是如何做入队,出队操作的呢?AQS调用自己的acquireQueued()方法
acquireQueued()方法
final boolean acquireQueued(final Node node, int arg) { //addWaiter()新增的node节点,使用当前线程作为节点 boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); //p前驱节点 if (p == head && tryAcquire(arg)) { //前驱节点是head,再次尝试获取锁 setHead(node); //获取锁成功,将自己设置为head节点 p.next = null; // 删掉前驱节点的后继指向,便于GC回收 failed = false; return interrupted; //返回入队失败 } if (shouldParkAfterFailedAcquire(p, node) && //判断是否要挂起节点,如果需要挂起,将前驱节点waitstatu改为Node.SIGNAL(-1) parkAndCheckInterrupt()) //挂起线程 interrupted = true; //返回入队成功 } } finally { if (failed) cancelAcquire(node); } } } //这里注意循环第一遍的时候,前驱节点如果为head,那么会先获取尝试获取一次锁, //获取失败的话,将前驱节点的waitstatus设置为SIGNAL,表示 前驱节点的后继节点(即当前node) 需要挂起等待 // //在第二次循环的时候前驱节点如果为head,那么会再尝试获取一次锁, //获取失败的话,并且因为waitstatus已设置为SIGNAL,那么会将当前线程挂起
reentranlock的unlock
方法unlock调用到:AbstractQueuedLongSynchronizer.release()
同理调用子类的tryRelease去释放锁,释放成功后调用unparkSuccessor唤醒队列的后继线程
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
我们看unparkSuccessor()
private void unparkSuccessor(Node node) { //node为head节点 int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); //修改状态为0,表示后继节点不再等待 Node s = node.next; if (s == null || s.waitStatus > 0) { //如果后继节点被取消,或者为空,从尾部往前找最后一个等待节点,将其唤醒 s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); //后继节点不为空,唤醒后继节点 }