目录

一、AQS原理简述

二、自定义独占锁及共享锁

三、锁的可重入性

四、锁的公平性

五、惊群效应


AQS全称AbstractQueuedSynchronizer,它是实现 JCU包中几乎所有的锁、多线程并发以及线程同步器等重要组件的基石, 其核心思想是基于volatile int state这样的一个属性同时配合Unsafe工具对其原子性的操作来实现对当前锁的状态进行修改 。

一、AQS原理简述

AQS内部维护着一个FIFO的CLH队列(无锁队列:Concurrent Lock-free FIFO),该队列的基本结构如下:

1.1、Node节点

AQS中使用Node来表示CLH队列的每个节点,源码如下:

    static final class Node {//表示共享模式(共享锁)static final Node SHARED = new Node();//表示独占模式(独占锁)static final Node EXCLUSIVE = null;//表示线程已取消static final int CANCELLED =  1;//表示当前结点的后继节点需要被唤醒static final int SIGNAL    = -1;//线程(处在Condition休眠状态)在等待Condition唤醒static final int CONDITION = -2;//表示锁的下一次获取可以无条件传播,在共享模式头结点有可能处于这种状态static final int PROPAGATE = -3;//线程等待状态volatile int waitStatus;//当前节点的前一个节点volatile Node prev;//当前节点的下一个节点volatile Node next;//当前节点所代表的的线程volatile Thread thread;//可以理解为当前是独占模式还是共享模式Node nextWaiter;//如果节点在共享模式下等待,则返回true。final boolean isShared() {return nextWaiter == SHARED;}//获取前一个节点final Node predecessor() throws NullPointerException {Node p = prev;if (p == null)throw new NullPointerException();elsereturn p;}...}

1.2、入队

如果当前线程通过CAS获取锁失败,AQS会将该线程以及等待状态等信息打包成一个Node节点,并将其加入同步队列的尾部,同时将当前线程挂起。总体流程图如下。

获取锁失败并添加节点到同步队列尾部的操作

1.3、出队

当释放锁时,执行出队操作及唤醒后继节点。总体流程图如下。

释放锁时的移除节点操作

1.4、同步状态管理

或许对图中的同步器有所疑惑。它到底是什么?其实很简单,它就是给首尾两个节点加上volatile同步域,如下。

private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;

上面三个变量是AQS中非常重要的三个变量,前面两个变量好理解,下面就来说一下state变量,该变量是一个计数器,在独占锁情况下,获取锁后,state的值就会为1,释放锁时就设置为0(这种锁属于不可重入锁);在共享锁情况下,每一个线程获取到锁,就会state++,释放锁时就state–;在可重入锁情况下(获取的锁都是同一把锁),每获取一次锁会state++,释放锁时state–。

protected final int getState() {return state;
}
protected final void setState(int newState) {state = newState;
}
//使用CAS
protected final boolean compareAndSetState(int expect, int update) {// See below for intrinsics setup to support thisreturn unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

二、自定义独占锁及共享锁

通过上一节的讲解,想必对AQS有了一定的了解,下面就通过AQS来实现一个独占锁及共享锁。AQS非常强大,只需要重写tryAcquiretryRelease这两个方法就可以实现一个独占锁。源码如下:

public class SingleLock implements Lock {//自定义的独占锁static class Sync extends AbstractQueuedSynchronizer {//独占锁@Overrideprotected boolean tryAcquire(int arg) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}//独占锁@Overrideprotected boolean tryRelease(int arg) {setExclusiveOwnerThread(null);setState(0);return true;}//判断是是否是独占锁。@Overrideprotected boolean isHeldExclusively() {return getState() == 1;}Condition newCondition() {return new ConditionObject();}}private Sync sync;public SingleLock() {sync = new Sync();}//加锁@Overridepublic void lock() {sync.acquire(1);}//获取可中断锁@Overridepublic void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}//获取锁,可能失败@Overridepublic boolean tryLock() {return sync.tryAcquire(1);}//在time时间内不能获取锁则失败@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(time));}//释放锁@Overridepublic void unlock() {sync.release(1);}//Condition来实现阻塞唤醒机制@Overridepublic Condition newCondition() {return sync.newCondition();}
}

很简单的代码就实现了一个独占锁,SingleLock拥有ReentrantLock的大部分功能,并且用法一模一样。是不是很简单…JUC包中提供的闭锁(CountDownLatch)及信号量(Semaphore)就是典型的共享锁的实现。共享锁的实现也很简单,需要重写tryAcquireSharedtryReleaseShared这两个方法。下面就来实现一个共享锁。代码如下:

public class ShareLock implements Lock {static class Sync extends AbstractQueuedSynchronizer {private int count;Sync(int count) {this.count = count;}@Overrideprotected int tryAcquireShared(int arg) {for (; ; ) {int current = getState();int newCount = current - arg;if (newCount < 0 || compareAndSetState(current, newCount)) {return newCount;}}}@Overrideprotected boolean tryReleaseShared(int arg) {for (; ; ) {int current = getState();int newCount = current + arg;if (compareAndSetState(current, newCount)) {return true;}}}Condition newCondition() {return new ConditionObject();}}private int count;private Sync sync;public ShareLock(int count) {this.count = count;sync = new Sync(count);}@Overridepublic void lock() {sync.acquireShared(1);}@Overridepublic void lockInterruptibly() throws InterruptedException {sync.acquireSharedInterruptibly(1);}@Overridepublic boolean tryLock() {return sync.tryAcquireShared(1) >= 0;}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(time));}@Overridepublic void unlock() {sync.releaseShared(1);}@Overridepublic Condition newCondition() {return sync.newCondition();}
}

ShareLock允许count个线程同时获取锁,它的实现也很简单吧。通过上面这两个例子,我们就可以按照自己需求来实现不同的锁,但JUC包中提供的类基本上能满足绝大部分需求了。

三、锁的可重入性

SingleLock是我们自己实现的一种独占锁,但如果把它用在递归中,就会产生死锁。因为SingleLock不具备可重入性。那么该如何实现可重入性尼?来看ReentrantLock的实现。

    final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}//可重入性的实现,如果当前线程已拿到锁else if (current == getExclusiveOwnerThread()) {//状态加1int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");//重新设置状态setState(nextc);return true;}return false;}

可以发现可重入性的实现还是蛮简单的,首先判断当前线程是不是已经拿到锁,如果已经拿到锁就将state的值加1。可重入性这一点非常重要,否则会产生不必要的死锁问题,Synchronize也具备可重入性。

四、锁的公平性

SingleLock属于一个非公平锁,那么如何实现公平锁尼?其实这更简单,只需要加个判断即可。来看ReentrantLock的公平锁的实现。

        protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//如果当前线程之前还有节点则hasQueuedPredecessors返回true,就不会去竞争锁if (!hasQueuedPredecessors() &&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;}

hasQueuedPredecessors就是判断锁是否公平的关键,如果在当前线程之前还有排队的线程就返回true,这时候当前线程就不会去竞争锁。从而保证了锁的公平性。

五、惊群效应

在使用wait/notify/notifyAll时,唤醒线程都是使用notifyAll来唤醒线程,因为notify无法唤醒指定线程,从而可能导致死锁。但使用notifyAll也有一个问题,那就是当大量线程来获取锁时,就会产生惊群效应,大量的竞争必然造成资源的剧增和浪费,因此终究只能有一个线程竞争成功,其他线程还是要老老实实的回去等待。而AQS的FIFO的等待队列给解决在锁竞争方面的惊群效应问题提供了一个思路:保持一个FIFO队列,队列每个节点只关心其前一个节点的状态,线程唤醒也只唤醒队头等待线程。

【参考资料】
深入学习java同步器AQS
浅谈Java并发编程系列(九)—— AQS结构及原理分析
Java并发编程之AQS
扒一扒ReentrantLock以及AQS实现

Java并发编程—AQS原理分析相关推荐

  1. Java并发编程学习 + 原理分析(建议收藏)

    总结不易,如果对你有帮助,请点赞关注支持一下 微信搜索程序dunk,关注公众号,获取博客源码 Doug Lea是一个无私的人,他深知分享知识和分享苹果是不一样的,苹果会越分越少,而自己的知识并不会因为 ...

  2. Java并发编程—ScheduledThreadPoolExecutor原理分析

    原文作者:小付 原文地址:ScheduledThreadPoolExecutor原理分析 目录 一.简单使用 二.类UML图 三.处理流程 四.任务提交方式 五.SchduledFutureTask之 ...

  3. Java 并发编程-不懂原理多吃亏(送书福利)

    作者 | 加多 关注阿里巴巴云原生公众号,后台回复关键字"并发",即可参与送书抽奖! ** 导读:并发编程与 Java 中其他知识点相比较而言学习门槛较高,从而导致很多人望而却步. ...

  4. Java并发编程AQS详解

    本文内容及图片代码参考视频:https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788.recommend_more_video. ...

  5. java并发编程-AQS

    1.什么是AQS AQSjava.util.concurrent.locks.AbstractQueuedSynchronizer抽象队列同步器 ,是用来构建锁或者其他同步组件的基础框架.它使用了一个 ...

  6. Java 并发编程AQS基本介绍

    what-什么是AQS? AQS是AbstractQueuedSynchronizer的简称.直译就是"抽象队列同步器". 它定义了一套多线程访问共享资源的同步框架,需要同步类实现 ...

  7. Java并发编程-AQS源码之条件队列

    System.out.println(name + "==>成功获取到锁" + lock); try { condition.await(); } catch (Interr ...

  8. Java 并发编程—有锁互斥机制及AQS理论

    原文作者:Java并发编程 原文地址:AQS这样学就很简单了 目录 一.有锁互斥机制 二.AQS如何实现互斥 三.结语 如果你是道格李,你要实现一套机制来保证线程互斥,你会如何实现呢?你肯定不会一上来 ...

  9. Java并发编程—常见面试题

    建议: 学习java并发前需要先掌握JVM知识 关于下面问题档案的详细解析都在后面推荐的相关系列文章中 一.线程安全相关 1.什么叫线程安全? 线程安全就是说多线程访问同一代码,不会产生不确定的结果. ...

最新文章

  1. 网页设计和用户界面设计
  2. Docker网络详解——原理篇
  3. grep的java源程序_Java实现Grep
  4. 对 带头结点的单链表 的操作
  5. 让我们拭目以待的jinbiguandan
  6. 解决python导入当前文件夹下的包时可以运行,但编辑器报错
  7. Pandas系列(十五)stack和pivot实现数据透视
  8. Scott Mitchell 的ASP.NET 2.0数据操作教程之九:跨页面的主/从报表
  9. Origin | 比例弦图 (Chord Diagram) | 比例弦图与弦图的区别
  10. 微信聊天记录生成词云图
  11. KeilC51版与KeilMDK版合并
  12. Python:实现monte carlo蒙特卡罗算法(附完整源码)
  13. 3-arm裸机存储器控制器之SDRAM
  14. R软件做线性回归分析
  15. Vue2.0 —— 运用算法实现 AST 抽象语法树
  16. 4、使用bean的scop属性来配置bean的作用域
  17. 文件的元数据信息的含义及查看和修改
  18. Linux 常用小工具
  19. 压力传感器电阻应变片的发展
  20. 案例 | 中央企业数字化转型实践

热门文章

  1. 编译器角度看C++复制构造函数
  2. c# 过滤HTML代码 源代码,案例 下载
  3. Java 理论与实践: 线程池与工作队列
  4. 如何通过shell脚本操作MongoDB
  5. 【python】解析Excel中使用xlrd库、xlwt库操作,使用xluils库修改Excel文件详解(三)...
  6. 大二暑假周进度总结07
  7. Entity Framework Core 之简单介绍
  8. 去黑头的7个必胜秘方
  9. 英文简历的几种常见形式
  10. 33.Node.js 文件系统fs