谢谢原作者 : singleAnt,写的非常好【java并发】juc高级锁机制探讨
1. Java中 的高级锁 :
RentrantLock :// 可重入的锁 ,它是一种独占锁 , 只能有一个对象拥有该锁,区别于synchronize,可中断 ,可定时,可公平的获得锁
RentrantReadWriteLock : // 可重入的读写锁,多个线程可以统一同时读,读是共享的,但写是独占的
Semaphore : // 信号量,它是一个共享锁,可以把他想象成一个池,里面放着当前可以使用的资源,多所有线程都是一样的,没有谁独占
CountDownLatch : // 共享锁 ,只有当count -> 0 , 那么 await()才允许线程通过
CyclicBarrier : // 共享锁 , 与CountDownLatch 类似,它是累加至相应的paties , 才通过
2. 这些锁的共性 : // 模板方法
多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足 xxxx 条件,才能触发线程执行 xxxx ;
像ReentrantLock ,共享状态就是对象的锁的是否被某个线程占用着,如果没有,那么当前线程占用,把state = 1
如果占用着,那么就看是不是这个线程占用着的,是的话,就将state + 1 , 如果不是就加入工作队列 ,wait,
在就如,CountDownLatech , 共享状态,就是count值,每一次调用countDown() ,count-- , 然后就会检查是否==0,
如果== 0 ,就会唤醒所有线程!
所以,把这个中间的过程抽象出来,定义骨架,然后具体实现,就各个锁(子类)自己去实现 === 这不就是模板方法吗?
而这个骨架 就是AQS框架,这些锁都是基于他创建的
下面来看ReentrantLock的基本原理:
ReentrantLock原理
由于同步器里已经定义了基本的结构,包括获取、释放、和阻塞队列维护和管理等。ReentrantLock是一个独占互斥锁,里只需要实现TryAcquire、TryRelease等方法,告诉同步器是否获取和释放状态成功。其他的后续行为都由AQS框架完成。由于ReentrantLock是一个可重入的独占锁,所以同步器状态可以直接根据是否==0来判断是否可用。
ReentrantLock主要提供lock和unlcok两个方法。
而lock和unlock正是基于AQS的一个子类同步器来实现。里面sync同步器有两种实现,一种是公平锁,一种是非公平锁。默认是非公平锁,看看非公平锁实现tryAcquire
- final boolean nonfairTryAcquire(int acquires) {
- final Thread current = Thread.currentThread();
- int c = getState();
- if (c == 0) {//如果状态位为0,那么尝试获取
- if (compareAndSetState(0, acquires)) {//基于CAS获取和修改状态
- setExclusiveOwnerThread(current);//成功则设置当前线程为独占执行线程
- return true;
- }
- }
- else if (current == getExclusiveOwnerThread()) {//当前线程已是执行线程
- int nextc = c + acquires;//累加
- if (nextc < 0) // overflow
- throw new Error("Maximum lock count exceeded");
- setState(nextc);
- return true;
- }
- return false;//其他情况下代表获取失败
再看看公平锁的tryAcquire
- protected final boolean tryAcquire(int acquires) {
- final Thread current = Thread.currentThread();
- int c = getState();
- if (c == 0) {
- if (isFirst(current) &&
- 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;
- }
可以看看 非公平锁的不会根据FIFO,而公平锁会判断是否是第一个线程,根据FIFO来执行。
中间的调用过程 :
lock.lock()调用 NofairSync.lock() ,他就会检查,锁是否被占用了compareAndSetState(0,1),如果没有则
当前线程占有锁,如果占用了,调用AQS的acquire()方法,看获得锁的线程是不是当前线程,调用NoFairSync的tryAcquire(),是当前线程,重新设置state,返回true,否则,则会acquireQueue(),多的当前阻塞队列,入队!可能你会觉得当我们向他加锁的过程,要经过这么多步骤,而中间没有synchronize同步,那么,别的线程也可以并发的访问,怎么能做到加锁了?关键就在于volatile state变量 和 compareAndSetState() /CAS
他们保证了线程间的通信,也就是说,那个线程访问到了CAS这一步,他对State的操作,立马就能够对其他线程看到!保证这有一个线程得到锁!
与ReentrantLock有关的类:
3. 共享锁 和 独占锁
独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁。共享锁,则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。AQS的内部类Node定义了两个常量SHARED和EXCLUSIVE,他们分别标识 AQS队列中等待线程的锁获取模式。
4. 锁的公平性:
锁的公平与非公平,是指线程请求获取锁的过程中,是否允许插队。在公平锁上,线程将按他们发出请求的顺序来获得锁;而非公平锁则允许在线程发出请求后立即尝试获取锁,如果可用则可直接获取锁,尝试失败才进行排队等待。ReentrantLock提供了两种锁获取方式,FairSyn和NofairSync。结论:ReentrantLock是以独占锁的加锁策略实现的互斥锁,同时它提供了公平和非公平两种锁获取方式。
5.AQS框架:
AbstractQueuedSynchronizer 是一个抽象类,里面定义了同步器的基本框架,实现了基本的结构功能。只留有状态条件的维护由具体同步器根据具体场景来定制,如上面提到的 ReentrantLock 、 RetrantReadWriteLock和CountDownLatch 等等。
一个同步器至少需要包含两个功能:
1. 获取同步状态
如果允许,则获取锁,如果不允许就阻塞线程,直到同步状态允许获取。
2. 释放同步状态
修改同步状态,并且唤醒等待线程。
根据作者论文, aqs 同步机制同时考虑了如下需求:
1. 独占锁和共享锁两种机制。
2. 线程阻塞后,如果需要取消,需要支持中断。
3. 线程阻塞后,如果有超时要求,应该支持超时后中断的机制。
实现涉及基本技术原理
1. 状态位
提供 volatile 变量 state; 用于同步线程之间的共享状态。通过 CAS 和 volatile 保证其原子性和可见性。对应源码里的定义:
- /**
- * 同步状态
- */
- private volatile int state;
- /**
- *cas
- */
- protected final boolean compareAndSetState(int expect, int update) {
- // See below for intrinsics setup to support this
- return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
- }
2. 线程阻塞和唤醒
有别于wait和notiry。这里利用 jdk1.5 开始提供的 LockSupport.park() 和 LockSupport.unpark() 的本地方法实现,实现线程的阻塞和唤醒。
3. 阻塞线程节点队列 CHL Node queue 。
根据论文里描述, AQS 里将阻塞线程封装到一个内部类 Node 里。并维护一个 CHL Node FIFO 队列。 CHL队列是一个非阻塞的 FIFO 队列,也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性。实现无锁且快速的插入。关于非阻塞算法可以参考 Java 理论与实践: 非阻塞算法简介 。CHL队列对应代码如下:
- /**
- * CHL头节点
- */
- rivate transient volatile Node head;
- /**
- * CHL尾节点
- */
- private transient volatile Node tail;
Node节点是对Thread的一个封装,结构大概如下:
- static final class Node {
- /** 代表线程已经被取消*/
- static final int CANCELLED = 1;
- /** 代表后续节点需要唤醒 */
- static final int SIGNAL = -1;
- /** 代表线程在等待某一条件/
- static final int CONDITION = -2;
- /** 标记是共享模式*/
- static final Node SHARED = new Node();
- /** 标记是独占模式*/
- static final Node EXCLUSIVE = null;
- /**
- * 状态位 ,分别可以使CANCELLED、SINGNAL、CONDITION、0
- */
- volatile int waitStatus;
- /**
- * 前置节点
- */
- volatile Node prev;
- /**
- * 后续节点
- */
- volatile Node next;
- /**
- * 节点代表的线程
- */
- volatile Thread thread;
- /**
- *连接到等待condition的下一个节点
- */
- Node nextWaiter;
- }
AQS 源码
AQS实现了一个同步器的基本结构,下面以独占锁和非独占锁区分来看看 AQS 的几个主要方法:
独占模式
独占获取: tryAcquire 本身不会阻塞线程,如果返回 true 成功就继续,如果返回 false 那么就阻塞线程并加入阻塞队列。
- public final void acquire(int arg) {
- if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//获取失败,则加入等待队列
- selfInterrupt();
- }
独占且可中断模式获取:支持中断取消
- public final void acquireInterruptibly(int arg) throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- if (!tryAcquire(arg))
- doAcquireInterruptibly(arg);
- }
独占且支持超时模式获取: 带有超时时间,如果经过超时时间则会退出。
- public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- return tryAcquire(arg) ||
- doAcquireNanos(arg, nanosTimeout);
独占模式释放:释放成功会唤醒后续节点
- public final boolean release(int arg) {
- if (tryRelease(arg)) {
- Node h = head;
- if (h != null && h.waitStatus != 0)
- unparkSuccessor(h);
- return true;
- }
- return false;
- }
共享模式
共享模式获取
- public final void acquireShared(int arg) {
- if (tryAcquireShared(arg) < 0)
- doAcquireShared(arg);
可中断模式共享获取
- public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- if (tryAcquireShared(arg) < 0)
- doAcquireSharedInterruptibly(arg);
- }
共享模式带定时获取
- public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {
- if (Thread.interrupted())
- throw new InterruptedException();
- return tryAcquireShared(arg) >= 0 ||
- doAcquireSharedNanos(arg, nanosTimeout);
- }
共享锁释放
- public final boolean releaseShared(int arg) {
- if (tryReleaseShared(arg)) {
- doReleaseShared();
- return true;
- }
- return false;
- }
注意以上框架只定义了一个同步器的基本结构框架,的基本方法里依赖的 tryAcquire 、 tryRelease 、tryAcquireShared 、 tryReleaseShared 四个方法在 AQS 里没有实现,这四个方法不会涉及线程阻塞,而是由各自不同的使用场景根据情况来定制:
- protected boolean tryAcquire(int arg) {
- throw new UnsupportedOperationException();
- }
- protected boolean tryRelease(int arg) {
- throw new UnsupportedOperationException();
- }
- protected int tryAcquireShared(int arg) {
- throw new UnsupportedOperationException();
- }
- protected boolean tryReleaseShared(int arg) {
- throw new UnsupportedOperationException();
- }
从以上源码可以看出AQS实现基本的功能:
AQS虽然实现了acquire,和release方法是可能阻塞的,但是里面调用的tryAcquire和tryRelease是由子类来定制的且是不阻塞的可。以认为同步状态的维护、获取、释放动作是由子类实现的功能,而动作成功与否的后续行为时有AQS框架来实现。所以可以认为同步器实现了一下功能:
1.同步器基本范式、结构
2.状态获取、释放成功或失败的后续行为,如线程的阻塞、唤醒机制
3.线程阻塞队列的维护
状态获取、释放动作本身是由子类来定义的。
还有以下一些私有方法,用于辅助完成以上的功能:
final boolean acquireQueued(final Node node, int arg) :申请队列
private Node enq(final Node node) : 入队
private Node addWaiter(Node mode) :以mode创建创建节点,并加入到队列
private void unparkSuccessor(Node node) : 唤醒节点的后续节点,如果存在的话。
private void doReleaseShared() :释放共享锁
private void setHeadAndPropagate(Node node, int propagate):设置头,并且如果是共享模式且propagate大于0,则唤醒后续节点。
private void cancelAcquire(Node node) :取消正在获取的节点
private static void selfInterrupt() :自我中断
private final boolean parkAndCheckInterrupt() : park 并判断线程是否中断
相关推荐
《AQS同步器与Redisson锁在Java高并发API及SpringBoot中的应用》 在Java并发编程领域,AbstractQueuedSynchronizer(AQS)是一个非常重要的基础组件,它是Java并发包java.util.concurrent中实现锁和同步器的核心...
AQS全称为AbstractQueuedSynchronizer,是java中用于构建锁以及其他同步器的一个框架。在多线程的编程中,同步问题是一个非常重要的问题,而AQS正是为了解决这个问题而生的。 首先,我们需要了解的是AQS的核心思想...
Java并发之AQS详解 AbstractQueuedSynchronizer(AQS)是 Java 并发编程中的一个核心组件,提供了一套多线程访问共享资源的同步器框架。AQS 定义了两种资源共享方式:Exclusive(独占)和 Share(共享)。在 AQS 中...
### JDK_AQS解析 #### 概述 在Java并发编程中,`AbstractQueuedSynchronizer`(简称AQS)是实现锁和其他同步工具的基础框架。AQS位于`java.util.concurrent`包下,通过模板方法设计模式实现了锁的底层机制。本文将...
从JUC中的AQS引入,讲解Java volatile与AQS锁内存可见性
java大师doug lean 在JDK1.5版本的AQS论文中文翻译。 许可:本作品的全部或部分在不为牟利或商业利益为目的的,且在第一页引述本声明及全完整引用的前提下,以数码或硬拷贝形式供个人或课堂使用的复制或分发不收取...
java锁AQS基础逻辑
AQS流程图ReentranLock.vsdx
### AQS核心原理与ReentrantLock的实现细节 #### AQS(AbstractQueuedSynchronizer)内部结构 AQS作为Java并发工具包(JUC)中的一个核心抽象类,其设计目的是为了实现各种同步器(如锁、信号量等)。AQS主要通过三...
juc 的aqs介绍。
`AQS`(AbstractQueuedSynchronizer)是JUC库中的一个关键组件,它是一个抽象基类,为构建自定义的同步器提供了基础框架。AQS通过内部维护一个基于链表的等待队列,有效地管理线程的同步和唤醒,从而实现锁和其他同步...
本文深入探讨了Java并发编程的关键组件——抽象队列同步器(AQS)及其在ReentrantLock的应用。AQS是处理线程同步问题的高效工具,是Java并发编程中的核心。文章首先简要介绍了并发编程领域的先驱Doug Lea。重点在于...
《Java并发编程:深入理解AQS》 在Java编程领域,多线程和并发处理是不可或缺的一部分,而Java.util.concurrent库则是实现并发控制的核心工具。本文将深入探讨该库中的重要组件——AbstractQueuedSynchronizer(AQS...
AQS源码阅读笔记 AQS(AbstractQueuedSynchronizer)是Java并发编程中的一种同步器框架,它提供了一个队列来管理线程的排队和唤醒机制。下面是AQS源码阅读笔记的详细解释: 1. `ReentrantLock` 的 `unlock()` 方法...
【Java AQS详解】 AbstractQueuedSynchronizer (AQS) 是 Java 并发库中的一个核心组件,它是实现高效并发控制的基础。AQS 提供了一种基于队列的线程同步机制,允许开发者构建自定义的锁和同步器。在Java并发编程中...
《AQS的底层原理》 在Java并发编程领域,AbstractQueuedSynchronizer(简称AQS)是一个核心组件,它是Java并发库中的基石,被许多并发工具类如ReentrantLock、Semaphore、CountDownLatch等作为基础框架来实现。AQS...
《AQS和JUC知识点详解》 在Java并发编程领域,AbstractQueuedSynchronizer(AQS)和Java Util Concurrency(JUC)是两个至关重要的概念。它们为开发高效、线程安全的多线程程序提供了强大的工具。本文将深入解析这...