`
spjich
  • 浏览: 95233 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

Java concurrent 之 AQS

    博客分类:
  • j2ee
阅读更多
转自:http://www.blogjava.net/xylz/archive/2010/07/06/325390.html

AQS

AbstractQueuedSynchronizer,简称AQS,是J.U.C最复杂的一个类,导致绝大多数讲解并发原理或者实战的时候都不会提到此类。但是虚心的作者愿意借助自己有限的能力和精力来探讨一二(参考资源中也有一些作者做了部分的分析。)。

首先从理论知识开始,在了解了相关原理后会针对源码进行一些分析,最后加上一些实战来描述。

image

上面的继承体系中,AbstractQueuedSynchronizer是CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基础,因此AbstractQueuedSynchronizer是Lock/Executor实现的前提。公平锁、不公平锁、Condition、CountDownLatch、Semaphore等放到后面的篇幅中说明。

完整的设计原理可以参考Doug Lea的论文 The java.util.concurrent Synchronizer Framework ,这里做一些简要的分析。

基本的思想是表现为一个同步器,支持下面两个操作:

获取锁:首先判断当前状态是否允许获取锁,如果是就获取锁,否则就阻塞操作或者获取失败,也就是说如果是独占锁就可能阻塞,如果是共享锁就可能失败。另外如果是阻塞线程,那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态,并且如果进了队列就从队列中移除。

while(synchronization state does not allow acquire){

    enqueue current thread if not already queued;

    possibly block current thread;

}

dequeue current thread if it was queued;

释放锁:这个过程就是修改状态位,如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程。

update synchronization state;

if(state may permit a blocked thread to acquire)

    unlock one or more queued threads;

要支持上面两个操作就必须有下面的条件:

  • 原子性操作同步器的状态位
  • 阻塞和唤醒线程
  • 一个有序的队列

目标明确,要解决的问题也清晰了,那么剩下的就是解决上面三个问题。

状态位的原子操作

这里使用一个32位的整数来描述状态位,前面章节的原子操作的理论知识整好派上用场,在这里依然使用CAS操作来解决这个问题。事实上这里还有一个64位版本的同步器(AbstractQueuedLongSynchronizer),这里暂且不谈。

阻塞和唤醒线程

标准的JAVA API里面是无法挂起(阻塞)一个线程,然后在将来某个时刻再唤醒它的。JDK 1.0的API里面有Thread.suspend和Thread.resume,并且一直延续了下来。但是这些都是过时的API,而且也是不推荐的做法。

在JDK 5.0以后利用JNI在LockSupport类中实现了此特性。

LockSupport.park()
LockSupport.park(Object)
LockSupport.parkNanos(Object, long)
LockSupport.parkNanos(long)
LockSupport.parkUntil(Object, long)
LockSupport.parkUntil(long)
LockSupport.unpark(Thread)

上面的API中park()是在当前线程中调用,导致线程阻塞,带参数的Object是挂起的对象,这样监视的时候就能够知道此线程是因为什么资源而阻塞的。由于park()立即返回,所以通常情况下需要在循环中去检测竞争资源来决定是否进行下一次阻塞。park()返回的原因有三:

  • 其他某个线程调用将当前线程作为目标调用 unpark
  • 其他某个线程中断当前线程;
  • 该调用不合逻辑地(即毫无理由地)返回。

其实第三条就决定了需要循环检测了,类似于通常写的while(checkCondition()){Thread.sleep(time);}类似的功能。

有序队列

在AQS中采用CHL列表来解决有序的队列的问题。

imageAQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。

对于入队列(enqueue):采用CAS操作,每次比较尾结点是否一致,然后插入的到尾结点中。

do {

        pred = tail;

}while ( !compareAndSet(pred,tail,node) );

对于出队列(dequeue):由于每一个节点也缓存了一个状态,决定是否出队列,因此当不满足条件时就需要自旋等待,一旦满足条件就将头结点设置为下一个节点。

while (pred.status != RELEASED) ;

head  = node;

实际上这里自旋等待也是使用LockSupport.park()来实现的。

AQS里面有三个核心字段:

private volatile int state;

private transient volatile Node head;

private transient volatile Node tail;

其中state描述的有多少个线程取得了锁,对于互斥锁来说state<=1。head/tail加上CAS操作就构成了一个CHL的FIFO队列。下面是Node节点的属性。

volatile int waitStatus; 节点的等待状态,一个节点可能位于以下几种状态:

  • CANCELLED = 1: 节点操作因为超时或者对应的线程被interrupt。节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出。
  • SIGNAL = -1: 节点的继任节点是(或者将要成为)BLOCKED状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的继任节点。
  • CONDITION = -2:表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。
  • 0: 正常状态,新生的非CONDITION节点都是此状态。
  • 非负值标识节点不需要被通知(唤醒)。

volatile Node prev;此节点的前一个节点。节点的waitStatus依赖于前一个节点的状态。

volatile Node next;此节点的后一个节点。后一个节点是否被唤醒(uppark())依赖于当前节点是否被释放。

volatile Thread thread;节点绑定的线程。

Node nextWaiter;下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。

 

分享到:
评论

相关推荐

    深入理解Java中的AQS.docx

    AbstractQueuedSynchronizer(AQS)是Java并发编程库(java.util.concurrent)中的核心组件,它为实现锁和同步器提供了基础框架。AQS利用了一个内置的FIFO(先进先出)双端队列来管理线程的等待和唤醒。在这个队列中...

    The java.util.concurrent Synchronizer Framework

    ### Java.util.concurrent.Synchronizer框架详解 #### 一、引言与背景 随着Java技术的发展,多线程编程成为了一项重要的技术需求。为了更好地支持并发编程,Java平台在J2SE 1.5版本中引入了`java.util.concurrent`...

    The java. util. concurrent synchronizer framework.pdf

    AQS(AbstractQueuedSynchronizer)是Java.util.concurrent包中同步器的基础框架,它的核心设计思想与实现方法在Doug Lea先生的这篇论文中有详细的介绍。论文详细阐述了AQS框架的原理、设计、实现、应用以及性能等...

    aqs_java_

    9. **子类实现**:Java.util.concurrent库中的很多同步组件,如ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等,都是基于AQS实现的。它们通过继承AQS并实现特定的同步逻辑,从而提供不同的同步功能。 ...

    JDK_AQS解析

    AQS位于`java.util.concurrent`包下,通过模板方法设计模式实现了锁的底层机制。本文将详细解析AQS中的关键方法以及其工作原理。 #### 锁的类图与架构 AQS采用模板方法模式,大多数与锁相关的操作都在`...

    aqs_demo.rar

    在Java并发编程领域,AbstractQueuedSynchronizer(AQS)是一个非常重要的基础组件,它是Java并发包java.util.concurrent中实现锁和同步器的核心工具类。AQS通过维护一个FIFO的等待队列来管理线程的同步状态,它提供...

    javaconcurrent源码-java7-source-code:Java7源码/Concurrency同步

    AQS 锁的公共类 20180514 String, 部分Character 20180508 除 Set 外, 常用的 Collection 都已经分析完毕 简化语言描述, 增加测试用例(实践用法) 接触到新的类再看源码(不能脱离实际场景瞎看, 容易迷茫没有方向) ...

    The java.util.concurrent synchronizer framework.pdf

    文档标题“java.util.concurrent同步器框架”和描述“Doug Lea的java.util.concurrent同步器框架”表明本文将探讨由Doug Lea所撰写的关于Java并发编程中同步器框架的内容。文档中提到了AbstractQueuedSynchronizer类...

    Java-并发(Concurrent)编程

    Java并发编程是开发高效应用程序的关键技能之一,尤其在如今的多核处理器环境下,理解并熟练掌握并发技术至关重要。本文将深入探讨并发编程的核心概念、工具和实现机制。 首先,我们来了解一下多线程。多线程是编程...

    Java 多线程与并发(10-26)-JUC锁- 锁核心类AQS详解.pdf

    Java并发包(java.util.concurrent,简称JUC)提供了一系列工具和类库来帮助开发者简化并发编程的工作。其中,AbstractQueuedSynchronizer(简称AQS)是构建各种同步器的核心组件。 AQS是一个抽象的队列同步器,它...

    java8源码-java-tutorial:Java基础实战教程,包含Java并发包JUC,AQS实战,设计模式实战,基础面试实战,IO/NI

    dive-in-concurrent 并发相关实战教程 dive-in-design-pattern 设计模式相关实战教程 dive-in-interview Java面试相关 dive-in-io IO/NIO/AIO相关 dive-in-java8 Java8新特性相关 dive-in-jvm JVM相关 java-practice...

    JUC AQS的加解锁.pdf

    Java的并发编程是多线程和多任务处理的核心技术之一,而在Java并发包 java.util.concurrent 中,AQS(AbstractQueuedSynchronizer)扮演了至关重要的角色。AQS是一种框架,用来构建锁或其他同步组件的基础。它提供了...

    7 AQS源码分析.docx

    它基于一种称为CLH(Craig, Landin, and Hagersten)队列的等待队列实现,是Java并发包`java.util.concurrent.locks`中的核心类。本文将详细分析AQS的源码,探讨其工作机制,以及在Java中如何实现不同类型的锁。 ...

    深入学习Java同步机制中的底层实现

    在Java中,主要有两种同步机制:内置的`synchronized`关键字以及基于`java.util.concurrent`包中的高级同步工具类。本文将深入探讨这些机制的底层实现,特别是通过`AbstractQueuedSynchronizer`(AQS)来理解`...

    带你看看Java-AQS同步器 源码解读四 条件队列Condition上

    在Java中,`java.util.concurrent.locks.Condition`接口是条件队列的抽象,它允许线程在等待特定条件时挂起,并在条件满足时被唤醒。与传统的`wait()`和`notify()`方法相比,`Condition`提供了更细粒度的控制,更...

    3.1.4.AQS底层原理分析1

    在Java.util.concurrent包中,许多并发工具类如ReentrantLock、Semaphore、CountDownLatch等都基于AQS实现。 AQS的核心概念是基于一个整型的state变量,它表示了资源的状态。当state为0时,表示资源没有被占用;非0...

Global site tag (gtag.js) - Google Analytics