AbstractQueuedSynchronized 以下简称AQS,是用来构建锁或者其他同步组件的基础框架。

  在AQS中,为锁的获取和释放提供了一些模板方法,而实现锁的类(AQS的子类)需要实现这些模板方法中的同步方法。

  这些方法包括:

  ·tryAcquire():尝试以独占模式获取锁

  ·tryRelease():尝试释放独占模式的锁

  ·tryAcquireShared():尝试以共享模式获取锁

  ·tryReleaseShared():尝试释放共享模式的锁

  ·isHeldExclusiverly():返回是否以独占模式获有锁

  在分析AQS的原理之前,我们先看看LockSupportLockCondition、AQS、ReentrantLok等等之间的关系和使用方式。

  关系图

  使用方式有两种:一种是不带条件的普通的锁,另一种是带条件的锁。

  不带条件

     /*
* ReentrantLock是Lock接口的实现类之一
* 实现的是一种可重入的锁
*/
Lock lock = new ReentrantLock();
lock.lock();
try {
//同步处理
}finally {
lock.unlock();
}

  带条件的锁

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();//创建和该锁关联的条件锁 public void conditionWait() throws InterruptedException{
lock.lock();
try {
condition.await();
}finally {
lock.unlock();
}
}
public void ConditionSignal() throws InterruptedException{
lock.lock();
try {
condition.signal();
}finally {
lock.unlock();
}
}

  从使用方式可以看到,主要调用的方法就是:lock.lock()、lock.unlock()、lock.newCondition()、condition.await()、condition.signal()

  

下面分别来看看这几个方法

  lock.lock():由于Lock是一个接口,所以需要通过其子类来实例化,所以lock.lock()其实调用的是子类的lock()方法,在上面的例子中自然调用的就是ReentrantLock.lock()方法。

    public void lock() {
sync.lock();
}

  可以看到,ReentrantLock.lock()方法调用的是同步器内部类的lock()方法,而ReentrantLock的内部同步器类Sync又分为FairSync和NoFairSync。

  源码如下(省略了一部分内容):

   abstract static class Sync extends AbstractQueuedSynchronizer {

        abstract void lock();
}
static final class NonfairSync extends Sync { final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
} static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}

  可以看到在非公平的获取锁的方法中,会首先尝试直接去获取锁,而不会通过同步队列去排队获取锁。否则的话,就通过AQS同步器的acquire(1)去获取锁。

  ※RetrantLock在初始化的时候可以通过参数指定是公平的还是不公平的锁。默认是非公平的锁。

  lock.unlock():调用的是同步器中的release(1)方法,也就是AQS中的release(1)方法。

    public void unlock() {
sync.release(1);
}

  lock.newCondition():该方法调用的也是内部同步器类中的newCondition()方法。

    public Condition newCondition() {
return sync.newCondition();
}

  在ReentrantLock的内部同步器类中的newCondition()方法如下:

        final ConditionObject newCondition() {
return new ConditionObject();
}

  可以看到,该方法直接返回了一个AQS中的内部类ConditonObject对象。所以,在每次生成一个条件锁的时候,都会创建一个ConditionObject对象,

  而每个ConditionObject对象都会在内部维护一个条件等待队列。

  ConditonObject实现分析点此参考

  conditon.await():通过调用LockSupport中的park()方法,将当前挂起。

  源码如下:

       public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

  condition.signal():通过调用LockSupport的unpark()方法,来唤醒线程。

  源码如下:

        public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
} private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
  final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false; /*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}

  了解完上面的概述之后,我们知道了,在不带条件的锁中,主要是通过调用AQS中的acquire(1)herelease(1)方法来获取锁和释放锁的。

下面就来具体看看AQS中的实现

  在AQS中通过一个双向FIFO同步队列来维护获取对象锁的线程,当获取锁失败的时候,会用当前线程构建一个Node节点,加入到同步队列中去。

  在AQS中维护了同步队列的head和tail节点,和同步状态。。

  Node节点中维护了当前线程的status和前驱节点、后继节点、下一个等待节点(条件等待的时候用)。

  waitStatus包含如下状态默认值为0:

    CANCELLED = 1 : 表示当前线程在等待的时候已经超时了或者被取消了

    SIGNAL = -1  :当前线程释放了同步状态或者被取消的时候会通知后继节点,使后继节点得以运行

    CONDITINO = -2:节点在等待队列中,等待Condition,当其他线程在Condition上调用signal的时候,该线程会从等待队列转移到同步队列中去,加入到同步状态的获取。

    PROPAGATE = -3:表示下一次共享式同步状态会无条件的传播下去

  AQS中同步器的结构如下:

  

  当有节点加入到同步队列的时候,只需要对tail节点重新指向就可以了

  同步队列是一个FIFO的队列,在获取锁的时候总是首节点是获取同步状态的节点,首节点的线程在释放同步状态时,将会唤醒后继节点,而后继节点在获取同步状态成功时,会将自己设置为首节点。

 下面就来具体看看在获取锁的时候lock.lock()调用的同步器中的acquire(1)方法的具体实现

    public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

  以独占模式获取锁,并且不相应中断。在AQS中还有以下acquire方法:

    acquireInterruptibly(int arg) :以独占模式获取锁,并且响应中断

    acquireShared(int arg):以共享模式获取锁,并且不响应中断

    acquireSharedInterruptibly(int arg):以共享模式获取锁,并且响应中断

  该方法首先会调用tryAcquire(arg)来尝试获取锁。从源码可以看到,在AQS中该方法只是单纯的抛出一个UnsupportedOperationException异常,该方法需要实现AQS同步器的具体类中实现。

    protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

  我们看看ReentrantLock中的具体实现。

  非公平锁:

        final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {  //如果当前的同步状态为0,就尝试直接设置同步状态和设置独占的线程为自己,来强制获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {  //如果当前同步状态不为0,就判断是不是自己获取了锁,这里是实现可重入的
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

  公平锁:

        protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {  
if (!hasQueuedPredecessors() &&  //当前state为0 ,并且同步队列中没有前继节点,就尝试设置自己为获得锁的线程
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {  //实现可重入
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

  可以看到,不论是在公平锁还是非公平锁的tryAcquire中,当获取到锁的时候返回的都是true,否则返回false。

  所以,如果当前线程没有获取到锁的时候,则会继续执行后面的acquireQueued(addWaiter(Node.EXCLUSIVE),arg))

  我们先看看addWaiter(Node.EXCLUSIVE)的实现。

    private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 首先为当前线程以指定模式构建一个Node节点,然后尝试快速入队的方式加入到同步队列中去
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);  //否则的话就调用enq(node)方法来加入到同步队列中去
return node;
}

  再看看enq(node)的源码实现

    private Node enq(final Node node) {
for (;;) {  //通过一个无条件的循环,知道将构建一个空的head,然后将当前节点加入到空的head的后面,构成一个同步队列
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}

  再看看acquireQueued(node,int)的源码实现

    final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {  //判断前一个节点是不是head节点,如果是的话,则会再次尝试去获取锁
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&  //如果前一个节点不是head节点,则设置前一个非取消节点的状态是signal,以确保在前一个线程释放锁的时候能唤醒当前线程
parkAndCheckInterrupt())  //挂起当前线程,并且返回当前线程是否被中断过(会清空中断状态,只有在被唤醒的时候才能从park()方法返回)
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);  //如果被中断过,则把该节点从同步队列中删除
}
}
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
    private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return; node.thread = null; // Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev; // predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next; // Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED; // If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
} node.next = node; // help GC
}
}

  acquire(1)的总结

   ①首先会去调用子类实现的具体tryAcquire(1)方法来获取锁,根据子类不同,实现的也不同,例如ReentrantLock中的实现就分为公平锁和非公平锁。非公平锁就是不会去同步队列中排队,

  而是直接去获取锁。如果获取失败的话,就会跟公平锁一样,进入FIFO的同步队列排队。

   ②加入同步队列的时候,首先会判断tail节点是否为空,如果不为空,则会尝试快速入队,如果为空的话,则会先创建一个空的head节点,然后在将当前线程的节点加入到同步队列中去

   ③加入到同步队列之后,会再次判断前一个节点是不是head节点,如果是的话,则会再次尝试去获取锁,如果获取失败的话,则会挂起当前线程。

   ④直到当前线程被唤醒的时候,会判断当前线程是否被中断过,如果被中断过,则会从同步队列中删除当前线程节点。并且中断当前线程。

  下面在看看lock.unlock()方法调用的同步器中的sync.release(1)方法的具体实现

    public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

  首先会调用tryRelease(arg)方法来释放锁。然后唤醒后面的线程。

    protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}

  tryRelease()同样也是子类需要实现的方法。ReentrantLock中的实现如下:

        protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

  

   private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); /*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
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);
}

  

  release(1)的总结

  ①首先会判断当前线程是不是独占的拥有锁。如果是的话,则会去释放锁。如果当前线程都已经退出了获取锁(可重入的原因),则会设置当前线程的state为0,独占线程为null

  ②在释放锁之后,会唤醒下一个等待中的线程。

  

  AQS中条件队列的实现方式参考ConditonObject实现分析点此参考

AbstractQueuedSynchronizer原理分析的更多相关文章

  1. AbstractQueuedSynchronizer 原理分析 - Condition 实现原理

    1. 简介 Condition是一个接口,AbstractQueuedSynchronizer 中的ConditionObject内部类实现了这个接口.Condition声明了一组等待/通知的方法,这 ...

  2. AbstractQueuedSynchronizer 原理分析 - 独占/共享模式

    1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ...

  3. AbstractQueuedSynchronizer 原理分析 - 独占/共享模式(转)

    1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ...

  4. JUC之AbstractQueuedSynchronizer原理分析 - 独占/共享模式

    1. 简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架. R ...

  5. Java 重入锁 ReentrantLock 原理分析

    1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...

  6. 转载:AbstractQueuedSynchronizer的介绍和原理分析

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

  7. AbstractQueuedSynchronizer的介绍和原理分析(转)

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

  8. AbstractQueuedSynchronizer原理及代码分析

    一.AQS简介 AbstractQueuedSynchronizer(AQS)是java.util.concurrent并发包下最基本的同步器,其它同步器实现,如ReentrantLock类,Reen ...

  9. 同步锁源码分析(一)AbstractQueuedSynchronizer原理

    文章转载自 AbstractQueuedSynchronizer的介绍和原理分析 建议去看一下原文的评论,会有不少收获. 简介 AbstractQueuedSynchronizer 提供了一个基于FI ...

随机推荐

  1. 【移动互联网开发】Zepto 使用中的一些注意点 【转】

    前段时间完成了公司一个产品的 HTML5 触屏版,开发中使用了 Zepto 这个著名的 DOM 操作库. 为什么不是 jQuery 呢?因为 jQuery 的目标是兼容所有主流浏览器,这就意味着它的大 ...

  2. Linux系统中DHCP的配置

    DHCP为动态主机配置协议,负责IP地址的动态分配(当一个主机的IP为自动,则不需要自己写IP,他会在DHCP服务器的范围内自动获取) 在真机或虚拟机下配置DHCP服务时,需要先下载dhcp软件,使用 ...

  3. 三张图看懂 clientheight、offsetheight、scrollheight

    clientheight clientheigh: 内容的可视区域,不包含border. clientheight=padding + height - 横向滚动轴高度. The Element.cl ...

  4. Shell 命令行获取本机IP,grep的练习

    Shell 命令行获取本机IP,grep的练习 在 mac 下面输入 ifconfig 或者在 linux 下面输入 ip a 就可以得到我们的网卡信息.不过通常情况下,我们需要查看的是我们的IP地址 ...

  5. 判断序列是否为二叉排序树的后序遍历 python

    题目:给定一个序列,判断其是不是一颗二叉排序树的后序遍历结果 分析:首先要知道什么是排序二叉树,二叉排序树是这样定义的,二叉排序树或者是一棵空树,或者是具有下列性质的二叉树: (1)若左子树不空,则左 ...

  6. HTML中Div、span、label标签的区别

    div与span 大家在初学div+css布局时,有很多困惑,在div与span的使用过程没觉得有一定的”章法”,觉得两个区别不大,在w3c的关于div和span的定义:div作为分割文档结构自然使它 ...

  7. OPEN(SAP) UI5 学习入门系列之三:MVC (上) - 模型

    这次我们来一起学习MVC,这个专题分为两个小节,本次主要是总览以及模型,下一次着重会介绍视图以及控制器,因为控制器其实没有太多可以讲的,所以和视图合并在一块. 1 Model View Control ...

  8. ibatis 参数类型为map,map里面有list

    <select id="getChannelLayerList" parameterClass="java.util.HashMap"        re ...

  9. Git钩子:自定义你的工作流

    Git钩子是在Git仓库中特定事件发生时自动运行的脚本.它可以让你自定义Git内部的行为,在开发周期中的关键点触发自定义的行为. Git钩子最常见的使用场景包括推行提交规范,根据仓库状态改变项目环境, ...

  10. ubuntu下codeblock美化

    1.备份配置文件:default.conf. 2.将default.conf中的内容替换为最下面代码,并保存. 3.打开codeblock,Setting-Editor-Syntax,然后选择Colo ...