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. C# 解决串口接收数据不完整

    方法1: 使 用缓存机制完成.首先通过定义一个成员变量List<byte> buffer = new List<byte> (4096);用来存放所有的数据,在接收函数里,通过 ...

  2. yii2 联系我们发送邮件报错

    为什么会报错,因为国内的邮件服务商要求发送邮件的人和设置的smtp服务器账号要相同,因为联系我们的是用户,也就是发件人是用户,而不是我们配置的邮箱,所有出错. 这里我用了个取巧的办法,发件人改为自己, ...

  3. Django中通过定时任务触发页面静态化的方式

    安装 pip install django-crontab 添加应用 INSTALLED_APPS = [ ... 'django_crontab', # 定时任务 ... ] 设置任务的定时时间 在 ...

  4. DR模式下的高可用的LVS(LVS+keepalived)

    一.keepalived 在DR模式下,使用Keepalived实现LVS的高可用.Keepalived的作用是检测服务器的状态,如果有一台web服务器 宕机,或工作出现故障,Keepalived将检 ...

  5. C++解决error C4996报错

    今天用c++写了个数独程序,在编译过程中报了一个错误: 1>------ 已启动生成: 项目: sudoku, 配置: Debug Win32 ------1> main.cpp1> ...

  6. React之状态(state)与生命周期

    很多时候,我们的页面数据是动态的.所以,我们需要实时渲染页面: 一.用定时函数setInterval() 组件(输出当前时间): index.js: 这样每隔1秒页面就会重新渲染一次,这样传进去的时间 ...

  7. React-Native进阶_7.TextInput的使用实现搜索功能

    前面使用TabBar 实现了底部tab标签,通过stackNavigator 实现了页面跳转,接下来,使用TextInput 来实现一个搜索功能. TextInput 属性比较多,不一一介绍,具体可以 ...

  8. React-Native基础_2.样式Style

    2.样式Style 基本使用 方式1 直接在View 上面写style 内容 <View style={{ backgroundColor: '#07811d', flex: 1 }}> ...

  9. python常用模块之xml模块

    python常用模块之xml模块 xml是实现不同语言或程序之间进行数据交换的协议,跟json差不多,但json使用起来更简单,不过,在json还没诞生的年代,大家都是使用xml,目前很多传统公司的系 ...

  10. MySQL安装部署及调优

    MySQL安装 二进制安装 - mysql-5.5.49 mkdir /home/oldboy/tools -p cd /home/oldboy/tools/ rz #mysql-5.5.49-lin ...