AQS原理分析

一,AQS原理

  lock最常用的类就是ReentrantLock,其底层实现使用的是AbstractQueuedSynchronizer(AQS)

  简单来说AQS会把所有的请求线程构成一个CLH队列,当一个线程执行完毕(lock.unlock())时会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park()完成,而LockSupport.park()则调用sun.misc.Unsafe.park()本地方法,再进一步,HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

  与synchronized相同的是,CLH也是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系。原生的CLH队列是用于自旋锁,但Doug Lea把其改造为阻塞锁。 当有线程竞争锁时,该线程会首先尝试获得锁,这对于那些已经在队列中排队的线程来说显得不公平,这也是非公平锁的由来,与synchronized实现类似,这样会极大提高吞吐量。 如果已经存在Running线程,则新的竞争线程会被追加到队尾,具体是采用基于CAS的Lock-Free算法,因为线程并发对Tail调用CAS可能会导致其他线程CAS失败,解决办法是循环CAS直至成功。

二,加锁过程

  

 1 /**
 2       * 尝试获取独占锁,获取成功则返回
 3       * 获取失败则继续自旋获取锁,并且判断中断标识,如果中断标识为true,则设置线程中断
 4       * addWaiter方法把当前线程封装成Node,并添加到队列的尾部
 5       */
 6     public final void acquire(int arg) {
 7         if (!tryAcquire(arg) &&
 8             acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 9             selfInterrupt();
10     }

  1,nonfairTryAcquire方法将是lock方法间接调用的第一个方法,每次请求锁时都会首先调用该方法。

 1 final boolean nonfairTryAcquire(int acquires) {
 2     final Thread current = Thread.currentThread();
 3     int c = getState();
 4     if (c == 0) {
 5         if (compareAndSetState(0, acquires)) {
 6             setExclusiveOwnerThread(current);
 7             return true;
 8         }
 9     }
10     else if (current == getExclusiveOwnerThread()) {
11         int nextc = c + acquires;
12         if (nextc < 0) // overflow
13             throw new Error("Maximum lock count exceeded");
14         setState(nextc);
15         return true;
16     }
17     return false;
18 }

  该方法会首先判断当前状态,如果c==0说明没有线程正在竞争该锁,通过CAS设置该状态值为acquires,acquires的初始调用值为1,每次线程重入该锁都会+1,每次unlock都会-1,为0时释放锁。如果CAS设置成功,其他任何线程调用CAS都不会再成功,当前线程得到了该锁,该Running线程并未进入等待队列。 
  如果c !=0 但发现自己已经拥有锁,只是简单地++acquires,并修改status值,但因为没有竞争,所以通过setStatus修改,而非CAS,也就是说这段代码实现了偏向锁的功能,并且实现的较为简单。

  2,addWaiter方法负责把当前无法获得锁的线程包装为一个Node添加到队尾:

 1 private Node addWaiter(Node mode) {
 2     Node node = new Node(Thread.currentThread(), mode);
 3     // Try the fast path of enq; backup to full enq on failure
 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 }

  追加到队尾的动作分两步:

  如果当前队尾已经存在(tail!=null),则使用CAS把当前线程更新为tail
  如果当前Tail为null或则线程调用CAS设置队尾失败,则通过enq方法继续设置tail :  

 1 private Node enq(final Node node) {
 2     for (;;) {
 3         Node t = tail;
 4         if (t == null) { // Must initialize
 5             Node h = new Node(); // Dummy header
 6             h.next = node;
 7             node.prev = h;
 8             if (compareAndSetHead(h)) {
 9                 tail = node;
10                 return h;
11             }
12         }
13         else {
14             node.prev = t;
15             if (compareAndSetTail(t, node)) {
16                 t.next = node;
17                 return t;
18             }
19         }
20     }
21 } 

  该方法就是循环调用CAS,即使有高并发的场景,无限循环将会最终成功把当前线程追加到队尾(或设置队头)。总而言之,addWaiter的目的就是通过CAS把当前线程追加到队尾,并返回包装后的Node实例。

  3,acquireQueued的主要作用是把已经追加到队列的线程节点(addWaiter方法返回值)进行阻塞.

  但阻塞前又通过tryAccquire重试是否能获得锁,如果重试成功能则无需阻塞,直接返回  

 1 final boolean acquireQueued(final Node node, int arg) {
 2     try {
 3         boolean interrupted = false;
 4         for (;;) {
 5             final Node p = node.predecessor();
 6             if (p == head && tryAcquire(arg)) {
 7                 setHead(node);
 8                 p.next = null; // help GC
 9                 return interrupted;
10             }
11             if (shouldParkAfterFailedAcquire(p, node) &&
12                 parkAndCheckInterrupt())
13                 interrupted = true;
14         }
15     } catch (RuntimeException ex) {
16         cancelAcquire(node);
17         throw ex;
18     }
19 }

  仔细看看这个方法是个无限循环,感觉如果p == head && tryAcquire(arg)条件不满足循环将永远无法结束,当然不会出现死循环,奥秘在于第12行的parkAndCheckInterrupt会把当前线程挂起,从而阻塞住线程的调用栈。

  

1 private final boolean parkAndCheckInterrupt() {
2     LockSupport.park(this);
3     return Thread.interrupted();
4 } 

  4,如前面所述,LockSupport.park最终把线程交给系统(Linux)内核进行阻塞。当然也不是马上把请求不到锁的线程进行阻塞,还要检查该线程的状态,比如如果该线程处于Cancel状态则没有必要,具体的检查在shouldParkAfterFailedAcquire中:

  

 1  private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
 2       int ws = pred.waitStatus;
 3       if (ws == Node.SIGNAL)
 4           /*
 5            * This node has already set status asking a release
 6            * to signal it, so it can safely park
 7            */
 8           return true;
 9       if (ws > 0) {
10           /*
11            * Predecessor was cancelled. Skip over predecessors and
12            * indicate retry.
13            */
14    do {
15             node.prev = pred = pred.prev;
16     } while (pred.waitStatus > 0);
17             pred.next = node;
18     } else {
19           /*
20            * waitStatus must be 0 or PROPAGATE. Indicate that we
21            * need a signal, but don‘t park yet. Caller will need to
22            * retry to make sure it cannot acquire before parking.
23            */
24           compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
25       }
26       return false;
27   }

  

检查原则在于:

  • 规则1:如果前继的节点状态为SIGNAL,表明当前节点需要unpark,则返回成功,此时acquireQueued方法的第12行(parkAndCheckInterrupt)将导致线程阻塞
  • 规则2:如果前继节点状态为CANCELLED(ws>0),说明前置节点已经被放弃,则回溯到一个非取消的前继节点,返回false,acquireQueued方法的无限循环将递归调用该方法,直至规则1返回true,导致线程阻塞
  • 规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环,与规则2同

总体看来,shouldParkAfterFailedAcquire就是靠前继节点判断当前线程是否应该被阻塞,如果前继节点处于CANCELLED状态,则顺便删除这些节点重新构造队列。

三,最后概括下加锁流程,如图所示:

原文地址:https://www.cnblogs.com/superchong/p/11254262.html

时间: 2024-11-04 19:25:40

AQS原理分析的相关文章

Java并发AQS原理分析(一)

我们说的AQS就是AbstractQueuedSynchronizer,他在java.util.concurrent.locks包下,这个类是Java并发的一个核心类.第一次知道有这个类是在看可重入锁ReentrantLock中,在ReentrantLock中有一个内部类Sync继承于AbstractQueuedSynchronizer,是ReentrantLock的核心实现.在并发包中的锁几乎都是基于AQS来构建的,但是在看源码的时候就会发现他们并没有直接继承AbstractQueuedSyn

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

1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ReentrantLock.CountDownLatch 和 Semaphore 等都是基于 AQS 实现的.除此之外,我们还可以基于 AQS,定制出我们所需要的同步器. AQS 的使用方式通常都是通过内部类继承 AQS 实现同步功能,通过继承 AQS,可以简化同步器的实现.如前面所说,AQS 是很

AQS 原理以及 AQS 同步组件总结

1 AQS 简单介绍 AQS 的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks 包下面. AQS 是一个用来构建锁和同步器的框架,使用 AQS 能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的 ReentrantLock,Semaphore,其他的诸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask 等等皆是基于 AQS 的.当然,我们自己也能利用 AQ

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

1.简介 AbstractQueuedSynchronizer (抽象队列同步器,以下简称 AQS)出现在 JDK 1.5 中,由大师 Doug Lea 所创作.AQS 是很多同步器的基础框架,比如 ReentrantLock.CountDownLatch 和 Semaphore 等都是基于 AQS 实现的.除此之外,我们还可以基于 AQS,定制出我们所需要的同步器. AQS 的使用方式通常都是通过内部类继承 AQS 实现同步功能,通过继承 AQS,可以简化同步器的实现.如前面所说,AQS 是很

Java 重入锁 ReentrantLock 原理分析

1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生.ReentrantLock 的主要功能和 synchronized 关键字一致,均是用于多线程的同步.但除此之外,ReentrantLock 在功能上比 synchronized 更为丰富.比如 ReentrantLock 在加锁期间,可响应中断,可设置超时等. ReentrantLock 是我们

kafka producer实例及原理分析

1.前言 首先,描述下应用场景: 假设,公司有一款游戏,需要做行为统计分析,数据的源头来自日志,由于用户行为非常多,导致日志量非常大.将日志数据插入数据库然后再进行分析,已经满足不了.最好的办法是存日志,然后通过对日志的分析,计算出有用的数据.我们采用kafka这种分布式日志系统来实现这一过程. 步骤如下: 搭建KAFKA系统运行环境 如果你还没有搭建起来,可以参考我的博客: http://zhangfengzhe.blog.51cto.com/8855103/1556650 设计数据存储格式

android脱壳之DexExtractor原理分析[zhuan]

http://www.cnblogs.com/jiaoxiake/p/6818786.html内容如下 导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1.  需要动态调试 2.  对抗反调试方案 为了提高工作效率, 我们不希望把宝贵的时间浪费去和加固的安全工程师去做对抗.作为一个高效率的逆向分析师, 笔者是忍不了的,所以我今天给大家带来一种的新的脱壳方法——DexExtractor脱壳法. 资源地址: Dex

android脱壳之DexExtractor原理分析

导语: 上一篇我们分析android脱壳使用对dvmDexFileOpenPartial下断点的原理,使用这种方法脱壳的有2个缺点: 1.  需要动态调试 2.  对抗反调试方案 为了提高工作效率, 我们不希望把宝贵的时间浪费去和加固的安全工程师去做对抗.作为一个高效率的逆向分析师, 笔者是忍不了的,所以我今天给大家带来一种的新的脱壳方法--DexExtractor脱壳法. 资源地址: DexExtractor源码:https://github.com/bunnyblue/DexExtracto

Adaboost算法原理分析和实例+代码(简明易懂)

Adaboost算法原理分析和实例+代码(简明易懂) [尊重原创,转载请注明出处] http://blog.csdn.net/guyuealian/article/details/70995333     本人最初了解AdaBoost算法着实是花了几天时间,才明白他的基本原理.也许是自己能力有限吧,很多资料也是看得懵懵懂懂.网上找了一下关于Adaboost算法原理分析,大都是你复制我,我摘抄你,反正我也搞不清谁是原创.有些资料给出的Adaboost实例,要么是没有代码,要么省略很多步骤,让初学者