JUC之CAS
CAS(全称为CompareAndSwap,也有说是CompareAndSet,都差不多)是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,判断预期值和更改新值的整个过程是原子的。在JAVA中,CAS的实现全部在sun.misc.Unsafe类中的各个方法,调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能。
在传统方式中实现并发的手段是加锁,JAVA中的锁有synchronized和Lock(jdk1.5才有)。Lock是基于AQS和CAS实现的,这里先跳过。对于synchronized锁,JVM在执行它的时候会依赖操作系统的临界区机制。这样的话,每次执行到synchronized锁,都会经历用户态和内核态之间的切换。这个过程的消耗是很大的。而且,大多数时候synchronized锁住的操作是很细粒度的。为了细粒度的操作去经历用户态和内核态之间的切换是低效的做法。
其实最常见的就是我们需要并发修改某个变量值,举个常见的例子,窗口售票,不加锁的代码如下所示:
public class Test {
public static void main(String[] args) {
Stock stock = new Stock(10);
for (int i = 0; i < 5; i++) {
new Thread(stock, "窗口" + (++i)).start();
}
}
private static class Stock implements Runnable {
private volatile int count;
public Stock(int count) {
this.count = count;
}
@Override
public void run() {
for (;;) {
count--;
if (count < 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + count);
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
得到的结果:
(票数和线程太少,需要多跑几遍,多了几乎一遍就可以看出问题)
得到的结果很明显,一共卖出了12张票,其中红框中出现的数字就可以说明问题。出现问题的原因很简单,因为--count这个算式表达式并不是原子的。在一个线程对count进行计算赋值后,但还没有将新值推送到内存中时,另一个线程获取的count值还是原来的值,当这个线程拿着这个值去进行计算,就会出现上面的问题。(这个涉及到Java的内存模型JMM,有兴趣的可以自行了解)
在JDK1.5之前,我们想要解决这个问题,就只能使用synchronized进行加锁,如下:
public void run() {
for (;;) {
synchronized (this) {
count--;
}
if (count < 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + count);
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
但是如果仅仅是类似于--count这种并发计数功能,需要进行同步的操作粒度很细时,使用synchronized就大材小用了,不高效(即便现在synchronized经过很多优化,不再想最初那样耗资源,但是它毕竟是个锁,而且多个线程进行竞争的时候还是会变成重量级锁),而使用CAS来实现就会更加的轻量级,性能更好。先上代码再说
public class Test {
private static Unsafe unsafe;
public static void main(String[] args) throws Exception {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
unsafe = (Unsafe) theUnsafeField.get(null);
Stock stock = new Stock(10);
for (int i = 0; i < 5; i++) {
new Thread(stock, "窗口" + (++i)).start();
}
}
private static class Stock implements Runnable {
private volatile int count;
private static long countOffset;
public Stock(int count) {
this.count = count;
try {
countOffset = unsafe.objectFieldOffset(this.getClass().getDeclaredField("count"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
@Override
public void run() {
for (;;) {
int x = this.count;
int y = x - 1;
if (!unsafe.compareAndSwapInt(this, countOffset, x, y)) {
continue;
}
if (y < 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + y);
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
像上面代码一样,当我们使用CAS进行操作时,出现的效果和使用synchronized一样的。代码中使用了JAVA提供的sun.misc.Unsafe进行CAS操作,而且在代码总我使用反射进行获取Unsafe实例,之所以这样做,是因为JDK不想让我们开发者去直接使用Unsafe这个类,而且使用起来比较繁琐,他们给我们提供了一些封装好的类来供我们开发者使用,比如常用的java.util.concurrent.atomic.AtomicInteger、java.util.concurrent.atomic.AtomicBoolean、java.util.concurrent.atomic.AtomicIntegerArray。这些类中都有相同的特点,就是使用sun.misc.Unsafe进行CAS操作,内部进行了一些类似上面代码的封装,我们就以AtomicInteger进行代码演示。
public class Test {
public static void main(String[] args) throws Exception {
Stock stock = new Stock(10);
for (int i = 0; i < 5; i++) {
new Thread(stock, "窗口" + (++i)).start();
}
}
private static class Stock implements Runnable {
private volatile AtomicInteger count;
public Stock(int count) {
this.count = new AtomicInteger(count);
}
@Override
public void run() {
for (;;) {
int x = count.get();
int y = x - 1;
if (!count.compareAndSet(x, y)) {
continue;
}
if (y < 0) {
return;
}
System.out.println(Thread.currentThread().getName() + "售出一张票,剩余票数:" + y);
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
就像上面的代码一样,我们不必进行反射获取Unsafe,然后在获取字段在class中的偏移量这些繁琐的操作了,下面我们就去看看AtomicInteger的源码

看上面AtomicInteger的源码我们可以很明显的看的出来,在AtomicInteger进行类加载的时候,会通过sun.misc.Unsafe获取value这个变量在类文件中的偏移量,进行保存,跟我们直接使用Unsafe的操作是一样的。我们找到刚才使用的compareAndSet(x, y)方法的源码,
可以看到底层就是使用unsafe实例进行CAS操作。
AtomicInteger还有一些别的方法,比如getAndIncrement、getAndDecrement、getAndAdd、incrementAndGet、decrementAndGet等等,底层实际上还是使用的unsafe实例进行CAS操作,有兴趣的同学可以自己翻下源码看看,这里就不多说了。
总结:CAS的出现就是为了解决一些简单的并发操作,将比较、赋值作为一个原子操作记性处理,实现无锁化处理,节省资源开销。
JUC之CAS的更多相关文章
- 一段JAVA代码了解多线程,JUC、CAS原子性操作。
@Test public void testPaceController_multiThread() throws InterruptedException { final PaceControlle ...
- JUC
1.Java JUC简介 在Java5.0提供了java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池.异 ...
- juc-2.1-模拟CAS算法
package com.wf.zhang.juc; /* * 模拟 CAS 算法 */ public class TestCompareAndSwap { public static void mai ...
- volatile关键字与内存可见性&原子变量与CAS算法
1 .volatile 关键字:当多个线程进行操作共享数据时, 可以保证内存中的数据可见 2 .原子变量:jdk1.5后java.util.concurrent.atomic 包下提供常用的原子变量 ...
- Spring Cloud--尚硅谷2020最新版
Spring Cloud 初识Spring Cloud与微服务 在传统的软件架构中,我们通常采用的是单体应用来构建一个系统,一个单体应用糅合了各种业务模块.起初在业务规模不是很大的情况下,对于单体应用 ...
- java多线程系列 JUC原子类 CAS及原子类
根据数据类型,可以将JUC包中的原子操作类可以分为4类. 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;2. 数组类型: AtomicInteg ...
- 【Java_多线程并发编程】JUC原子类——原子类中的volatile变量和CAS函数
JUC中的原子类是依靠volatile变量和Unsafe类中的CAS函数实现的. 1. volatile变量的特性 内存可见性(当一个线程修改volatile变量的值后,另一个线程就可以实时看到此变量 ...
- java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)
这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...
- JUC原子操作类与乐观锁CAS
JUC原子操作类与乐观锁CAS 硬件中存在并发操作的原语,从而在硬件层面提升效率.在intel的CPU中,使用cmpxchg指令.在Java发展初期,java语言是不能够利用硬件提供的这些便利来提 ...
随机推荐
- split(resource,limit) 中limit 的含义
limit 参数控制模式应用的次数,因此影响结果数组的长度.如果该限制 n 大于 0,则模式将被最多应用 n - 1 次,数组的长度将不会大于 n,而且数组的最后项将包含超出最后匹配的定界符的所有输入 ...
- 基于 HTML5 WebGL 的 CPU 监控系统
前言 科技改变生活,科技的发展带来了生活方式的巨大改变.随着通信技术的不断演进,5G 技术应运而生,随时随地万物互联的时代已经来临.5G 技术不仅带来了更快的连接速度和前所未有的用户体验,也为制造业, ...
- Linux 下发送邮件
由于种种原因,需要由我这个兼职运维每天发送对账单文件给运营同学,故研究下 Linux 发送邮件,希望对大家有所帮助. 安装 # Centos,安装 mailx $ yum install -y mai ...
- 学习Saleforce | 业内第一份Salesforce学习者数据报告
自从自由侠部落这个Salesforce中文学习平台成立以来,我们接触到了越来越多的Salesforce的学习者,由衷感觉到这个学习生态圈愈发蓬勃发展. 为了了解Salesforce学习者的基本情况.现 ...
- CTR学习笔记&代码实现3-深度ctr模型 FNN->PNN->DeepFM
这一节我们总结FM三兄弟FNN/PNN/DeepFM,由远及近,从最初把FM得到的隐向量和权重作为神经网络输入的FNN,到把向量内/外积从预训练直接迁移到神经网络中的PNN,再到参考wide& ...
- How Many Answers Are Wrong HDU - 3038 (经典带权并查集)
题目大意:有一个区间,长度为n,然后跟着m个子区间,每个字区间的格式为x,y,z表示[x,y]的和为z.如果当前区间和与前面的区间和发生冲突,当前区间和会被判错,问:有多少个区间和会被判错. 题解:x ...
- 关于C++线程池的实现的思考
今天突然对前些日子一直很疑惑的c++线程池有了新的想法.其实所谓的线程池无非就是两个技术点,一个,多线程,指工作线程和主线程分离,或者说数据接收和处理分两个线程,一般就是讲需要运行的函数放到子线程执行 ...
- 今天我们来谈谈绝对定位和相对定位的区别,和需要注意的问题;position:absolute|relative;
首先position:absolute|relative; 前者是绝对定位,后者是相对定位: position属性的四个值: static,relative,fixed,absolute; 重点重点重 ...
- SpringBoot集成MyBatis底层原理及简易实现
MyBatis是可以说是目前最主流的Spring持久层框架了,本文主要探讨SpringBoot集成MyBatis的底层原理.完整代码可移步Github. 如何使用MyBatis 一般情况下,我们在Sp ...
- 彻底弄懂GMT、UTC、时区和夏令时
前言 格林威治时间.世界时.祖鲁时间.GMT.UTC.跨时区.夏令时,这些眼花缭乱的时间术语,我们可能都不陌生,但是真正遇到问题,可能又不那么确定,不得不再去查一查,处理完可能过段时间又忘记.今天,我 ...