JAVA并发编程: CAS和AQS
说起JAVA并发编程,就不得不聊聊CAS(Compare And Swap)和AQS了(AbstractQueuedSynchronizer
)。
CAS(Compare And Swap)
什么是CAS
CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
在JAVA中,sun.misc.Unsafe
类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent
包下的大量类都使用了这个 Unsafe.java
类的CAS操作。至于 Unsafe.java
的具体实现这里就不讨论了。
CAS典型应用
java.util.concurrent.atomic
包下的类大多是使用CAS操作来实现的(eg. AtomicInteger.java
,AtomicBoolean
,AtomicLong
)。下面以 AtomicInteger.java
的部分实现来大致讲解下这些原子类的实现。
public class AtomicInteger extends Number implements java.io.Serializable {private static final long serialVersionUID = 6214790243416807050L;// setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();private volatile int value;// 初始int大小// 省略了部分代码...// 带参数构造函数,可设置初始int大小public AtomicInteger(int initialValue) {value = initialValue;}// 不带参数构造函数,初始int大小为0public AtomicInteger() {}// 获取当前值public final int get() {return value;}// 设置值为 newValuepublic final void set(int newValue) {value = newValue;}//返回旧值,并设置新值为 newValuepublic final int getAndSet(int newValue) {/*** 这里使用for循环不断通过CAS操作来设置新值* CAS实现和加锁实现的关系有点类似乐观锁和悲观锁的关系* */for (;;) {int current = get();if (compareAndSet(current, newValue))return current;}}// 原子的设置新值为update, expect为期望的当前的值public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update);}// 获取当前值current,并设置新值为current+1public final int getAndIncrement() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return current;}}// 此处省略部分代码,余下的代码大致实现原理都是类似的
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
一般来说在竞争不是特别激烈的时候,使用该包下的原子操作性能比使用 synchronized
关键字的方式高效的多(查看getAndSet()
,可知如果资源竞争十分激烈的话,这个for循环可能换持续很久都不能成功跳出。不过这种情况可能需要考虑降低资源竞争才是)。
在较多的场景我们都可能会使用到这些原子类操作。一个典型应用就是计数了,在多线程的情况下需要考虑线程安全问题。通常第一映像可能就是:
public class Counter {private int count;public Counter(){}public int getCount(){return count;}public void increase(){count++;}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
上面这个类在多线程环境下会有线程安全问题,要解决这个问题最简单的方式可能就是通过加锁的方式,调整如下:
public class Counter {private int count;public Counter(){}public synchronized int getCount(){return count;}public synchronized void increase(){count++;}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
这类似于悲观锁的实现,我需要获取这个资源,那么我就给他加锁,别的线程都无法访问该资源,直到我操作完后释放对该资源的锁。我们知道,悲观锁的效率是不如乐观锁的,上面说了Atomic下的原子类的实现是类似乐观锁的,效率会比使用 synchronized
关系字高,推荐使用这种方式,实现如下:
public class Counter {private AtomicInteger count = new AtomicInteger();public Counter(){}public int getCount(){return count.get();}public void increase(){count.getAndIncrement();}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
AQS(AbstractQueuedSynchronizer
)
什么是AQS
AQS(AbstractQueuedSynchronizer
),AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。这个抽象类被设计为作为一些可用原子int值来表示状态的同步器的基类。如果你有看过类似 CountDownLatch
类的源码实现,会发现其内部有一个继承了 AbstractQueuedSynchronizer
的内部类 Sync
。可见 CountDownLatch
是基于AQS框架来实现的一个同步器.类似的同步器在JUC下还有不少。(eg. Semaphore
)
AQS用法
如上所述,AQS管理一个关于状态信息的单一整数,该整数可以表现任何状态。比如, Semaphore
用它来表现剩余的许可数,ReentrantLock
用它来表现拥有它的线程已经请求了多少次锁;FutureTask
用它来表现任务的状态(尚未开始、运行、完成和取消)
To use this class as the basis of a synchronizer, redefine the* following methods, as applicable, by inspecting and/or modifying* the synchronization state using {@link #getState}, {@link* #setState} and/or {@link #compareAndSetState}:** <ul>* <li> {@link #tryAcquire}* <li> {@link #tryRelease}* <li> {@link #tryAcquireShared}* <li> {@link #tryReleaseShared}* <li> {@link #isHeldExclusively}* </ul>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
如JDK的文档中所说,使用AQS来实现一个同步器需要覆盖实现如下几个方法,并且使用getState
,setState
,compareAndSetState
这几个方法来设置获取状态
1. boolean tryAcquire(int arg)
2. boolean tryRelease(int arg)
3. int tryAcquireShared(int arg)
4. boolean tryReleaseShared(int arg)
5. boolean isHeldExclusively()
以上方法不需要全部实现,根据获取的锁的种类可以选择实现不同的方法,支持独占(排他)获取锁的同步器应该实现tryAcquire
、 tryRelease
、isHeldExclusively
而支持共享获取的同步器应该实现tryAcquireShared
、tryReleaseShared
、isHeldExclusively
。下面以 CountDownLatch
举例说明基于AQS实现同步器, CountDownLatch
用同步状态持有当前计数,countDown
方法调用 release从而导致计数器递减;当计数器为0时,解除所有线程的等待;await
调用acquire,如果计数器为0,acquire
会立即返回,否则阻塞。通常用于某任务需要等待其他任务都完成后才能继续执行的情景。源码如下:
public class CountDownLatch {/*** 基于AQS的内部Sync* 使用AQS的state来表示计数count.*/private static final class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 4982264981922014374L;Sync(int count) {// 使用AQS的getState()方法设置状态setState(count);}int getCount() {// 使用AQS的getState()方法获取状态return getState();}// 覆盖在共享模式下尝试获取锁protected int tryAcquireShared(int acquires) {// 这里用状态state是否为0来表示是否成功,为0的时候可以获取到返回1,否则不可以返回-1return (getState() == 0) ? 1 : -1;}// 覆盖在共享模式下尝试释放锁protected boolean tryReleaseShared(int releases) {// 在for循环中Decrement count直至成功;// 当状态值即count为0的时候,返回false表示 signal when transition to zerofor (;;) {int c = getState();if (c == 0)return false;int nextc = c-1;if (compareAndSetState(c, nextc))return nextc == 0;}}}private final Sync sync;// 使用给定计数值构造CountDownLatchpublic CountDownLatch(int count) {if (count < 0) throw new IllegalArgumentException("count < 0");this.sync = new Sync(count);}// 让当前线程阻塞直到计数count变为0,或者线程被中断public void await() throws InterruptedException {sync.acquireSharedInterruptibly(1);}// 阻塞当前线程,除非count变为0或者等待了timeout的时间。当count变为0时,返回truepublic boolean await(long timeout, TimeUnit unit)throws InterruptedException {return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));}// count递减public void countDown() {sync.releaseShared(1);}// 获取当前count值public long getCount() {return sync.getCount();}public String toString() {return super.toString() + "[Count = " + sync.getCount() + "]";}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
本文大致就讲了这些东西,有些地方说的也不是特别好。也有不全的地方,AQS的东西还是有不少的,建议大家自己去看JUC下的各个类的实现,配合《JAVA并发编程实战》这本书,相信是可以看明白的,从而得到更深刻的理解。
JAVA并发编程: CAS和AQS相关推荐
- aqs clh java_【Java并发编程实战】—– AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...
- Java 并发编程CAS、volatile、synchronized原理详解
CAS(CompareAndSwap) 什么是CAS? 在Java中调用的是Unsafe的如下方法来CAS修改对象int属性的值(借助C来调用CPU底层指令实现的): /*** * @param o ...
- java并发编程——九 AbstractQueuedSynchronizer AQS详解
文章目录 AbstractQueuedSynchronizer概述 AbstractQueuedSynchronizer的使用 AQS实现分析 同步队列 独占锁的获取与释放 独占式超时获取 共享式锁的 ...
- aqs clh java_【Java并发编程实战】----- AQS(一):简介
在前面博客中,LZ讲到了ReentrantLock.ReentrantReadWriteLock.Semaphore.CountDownLatch,他们都有各自获取锁的方法,同时相对于Java的内置锁 ...
- Java并发编程——详解AQS对Condition接口的具体实现
目录 一.等待/通知机制与Condition接口 1.1 等待/通知机制 1.2 Condition接口 二.AQS的具体实现 2.1 ConditionObject 2.2 等待机制 2.3 通知机 ...
- Java并发编程—AQS原理分析
目录 一.AQS原理简述 二.自定义独占锁及共享锁 三.锁的可重入性 四.锁的公平性 五.惊群效应 AQS全称AbstractQueuedSynchronizer,它是实现 JCU包中几乎所有的锁.多 ...
- Java 并发编程—有锁互斥机制及AQS理论
原文作者:Java并发编程 原文地址:AQS这样学就很简单了 目录 一.有锁互斥机制 二.AQS如何实现互斥 三.结语 如果你是道格李,你要实现一套机制来保证线程互斥,你会如何实现呢?你肯定不会一上来 ...
- Java并发编程-无锁CAS与Unsafe类及其并发包Atomic
[版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/72772470 出自[zejian ...
- Java并发编程,无锁CAS与Unsafe类及其并发包Atomic
为什么80%的码农都做不了架构师?>>> 我们曾经详谈过有锁并发的典型代表synchronized关键字,通过该关键字可以控制并发执行过程中有且只有一个线程可以访问共享资源,其 ...
最新文章
- 记一次Quartz重复调度(任务重复执行)的问题排查
- java导出模板 pdf设置字体_有哪些相见恨晚的PPT模板网站?
- titanium开发教程-04-11其他属性和方法
- Python实现九九乘法表
- 复旦大学肖仰华教授:知识图谱与认知智能 | 附PPT下载
- php 5w的并发需要多少台服务器_php使用异步编程是怎样的?
- Java高并发系列 — AQS
- C++ vector用法
- 【读书笔记】非暴力沟通
- VS2017 提示警告 IDE0006
- Redis百亿级Key你用过吗?
- bulk insert
- 《深入学习VMware vSphere 6》——2.3 在VMware Workstation虚拟机中安装ESXi 6
- python爬取链家二手房信息并存储到数据库
- 陈宝存:楼市调控不撞南墙不回头
- html5使用websocket发送(PCM)音频数据到服务器,再转在wav文件
- python 判断一个list列表是否包含另一个list列表
- 盖茨和李嘉诚双龙卸甲 他们的区块链业务一个在链圈扶贫 一个在币圈赚钱
- 流场可视化工程dlb-dynamicdr部署日志:阶段二:工程本地编译
- NQA基本原理与配置
热门文章
- notepad json格式化插件_Prettier + ESLint VSCode 插件配置指南
- android 第三方登录界面,Android App集成第三方登录与换肤指南
- linux驱动导出文件属性,将Linux配置文件和设置备份到USB闪存驱动器的方法
- mysql flask-login_Flask web模板六–Flask-Login完成登录验证
- Linux wifi连接桌面,【已解决】Arch linux 安装之后在deepin桌面环境下使用networkmanager连接wifi 出现间歇性重连的情况...
- php群发不用foreach,如何在没有foreach的情况下使用PHP生成器?
- 计算机联网实验步骤,计算机网络技术实验操作过程.doc
- python读取序列5之后的数据_Python核心编程读笔 5: python的序列
- java servlet 输出_JavaWeb中servlet读取配置文件的方式
- 易语言 图片插入超级列表框_是谁说图片排版很难?掌握这4个PPT图片排版技巧,1分钟全部搞定...