Java中CAS 基本实现原理
一、前言
了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称。它有核心就是CAS与AQS。CAS是java.util.concurrent.atomic包的基础,如AtomicInteger、AtomicBoolean、AtomicLong等等类都是基于CAS。
什么是CAS呢?全称Compare And Swap,比较并交换。CAS有三个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。
二、实例
如果我们需要对一个数进行加法操作,应该怎样去实现呢?我们模拟多个线程情况下进行操作。
ThreadDemo.java 实现一个Runnable接口
package com.spring.security.test;
public class ThreadDemo implements Runnable {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
addCount();
}
}
private void addCount() {
count++;
}
public int getCount() {
return count;
}
}
ThreadTest.java 创建线程池,提交10个线程执行,预期结果应该是1000
package com.spring.security.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadTest {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10);
ThreadDemo threadDemo = new ThreadDemo();
for (int i = 0; i < 10; i++) {
threadPool.submit(threadDemo);
}
threadPool.shutdown();
System.out.println(threadDemo.getCount());
}
}
运行结果:874 或其他,与预期结果不符合。
执行出来的结果并不是想象中的结果。这是为什么呢?这跟线程的执行过程有关。
所以我们需要在改变count,将值从高速缓冲区刷新到主内存后,让其他线程重新读取主内存中的值到自己的工作内存。
此时可以用volatile关键字。它的作用是保证对象在内存中的可见性。
修改ThreadDemo中的count字段
private volatile int count = 0;
此时执行结果:900 或其他,与预期结果不符合。
此时还是并未得出正确执行结果。为什么?听我细细道来。
线程安全主要体现在三个方面:
- 原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
目前可见性已经实现了,缺少原子性的操作,因为同一时刻,多个线程对其操作,会将改动后的最新值读取到自己的工作内存进行操作,最终只能得到后一个执行线程操作的结果,所以相当于少了一步操作,就会造成数据的不一致。
此时可以使用JUC的Atomic包下面的类来进行操作。
Atomic类是使用CAS+volatile来实现原子性与可见性的。
我们来改造一下TheadDemo.java中的实现方法
package com.spring.security.test;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadDemo implements Runnable {
private AtomicInteger count = new AtomicInteger(0);
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 递增
count.getAndIncrement();
}
}
public int getCount() {
return count.get();
}
}
执行结果: 1000,符合预期值。
接下来我们来分析一下AtomicInteger类的源码:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
Unsafe类是不安全的类,它提供了一些底层的方法,我们是不能使用这个类的。AtomicInteger的值保存在value中,而valueOffset是value在内存中的偏移量,利用静态代码块使其类一加载的时候就赋值。value值使用volatile,保证其可见性。
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
var1表示当前对象,var2表示value在内存中的偏移量,var4为增加的值。var5为调用底层方法获取value的值
compareAndSwapInt方法通过var1和var2获取当前内存中的value值,并与var5进行比对,如果一致,就将var5+var4的值赋给value,并返回true,否则返回false
由do while语句可知,如果这次没有设置进去值,就重复执行此过程。这一过程称为自旋。
compareAndSwapInt是JNI(Java Native Interface)提供的方法,可以是其他语言写的。
三、与synchronized比较
使用synchronized进行加法:
package com.spring.security.test;
public class ThreadDemo implements Runnable {
private int count = 0;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// 递增
synchronized (ThreadDemo.class) {
count++;
}
}
}
public int getCount() {
return count;
}
}
运行结果: 1000,符合预期值。
使用synchronized和AtomicInteger都能得到预期结果,但是他们之间各有什么劣势呢?
synchronized是重量级锁,是悲观锁,就是无论你线程之间发不发生竞争关系,它都认为会发生竞争,从而每次执行都会加锁。
在并发量大的情况下,如果锁的时间较长,那将会严重影响系统性能。
CAS操作中我们可以看到getAndAddInt方法的自旋操作,如果长时间自旋,那么肯定会对系统造成压力。而且如果value值从A->B->A,那么CAS就会认为这个值没有被操作过,这个称为CAS操作的"ABA"问题。
Java中CAS 基本实现原理的更多相关文章
- Java中CAS 基本实现原理 和 AQS 原理
一.前言了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称.它有核心就是CAS与AQS.CAS是java.util.concurrent.at ...
- 从虚拟机指令执行的角度分析JAVA中多态的实现原理
从虚拟机指令执行的角度分析JAVA中多态的实现原理 前几天突然被一个"家伙"问了几个问题,其中一个是:JAVA中的多态的实现原理是什么? 我一想,这肯定不是从语法的角度来阐释多态吧 ...
- Java中CAS原理详解
在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. (2 ...
- Java中CAS原理分析(volatile和synchronized浅析)
CAS是什么? CAS英文解释是比较和交换,是cpu底层的源语,是解决共享变量原子性实现方案,它定义了三个变量,内存地址值对应V,期待值E和要修改的值U,如下图所示,这些变量都是在高速缓存中的,如果两 ...
- Java中Synchronized的优化原理
我们知道,从 JDK1.6 开始,Java 对 Synchronized 同步锁做了充分的优化,甚至在某些场景下,它的性能已经超越了 Lock 同步锁.那么就让我们来看看,它究竟是如何优化的. 原本的 ...
- java中jvm的工作原理
首先我们安装了jdk和jre,但是jdk是为java软件开发工程师而使用的开发工具,我们运行java项目只要含有jre文件即可.对于jvm是内存分配的一块区域,我们知道,当我们开始使用java命令时, ...
- Java中CAS详解
在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁 锁机制存在以下问题: (1)在多线程竞争下,加锁.释放锁会导致比较多的上下文切换和调度延时,引起性能问题. (2 ...
- Java中HashMap的实现原理
最近面试中被问及Java中HashMap的原理,瞬间无言以对,因此痛定思痛觉得研究一番. 一.Java中的hashCode和equals 1.关于hashCode hashCode的存在主要是用于查找 ...
- Java 中泛型的实现原理
泛型是 Java 开发中常用的技术,了解泛型的几种形式和实现泛型的基本原理,有助于写出更优质的代码.本文总结了 Java 泛型的三种形式以及泛型实现原理. 泛型 泛型的本质是对类型进行参数化,在代码逻 ...
随机推荐
- P6087 [JSOI2015]送礼物 01分数规划+单调队列+ST表
P6087 [JSOI2015]送礼物 01分数规划+单调队列+ST表 题目背景 \(JYY\) 和 \(CX\) 的结婚纪念日即将到来,\(JYY\) 来到萌萌开的礼品店选购纪念礼物. 萌萌的礼品店 ...
- 封装react antd的表格table组件
封装组件是为了能在开发过程中高度复用功能和样式相似的组件,以便我们只关注于业务逻辑层的处理,提高开发效率,提高逼格,降低代码重复率,降低劳动时间,减少加班的可能. 本次组件的封装采用了函数式组件即无状 ...
- 洛谷P1579.验证哥德巴赫猜想(DFS+素性测试)
题目背景 1742年6月7日哥德巴赫写信给当时的大数学家欧拉,正式提出了以下的猜想:任何一个大于9的奇数都可以表示成3个质数之和.质数是指除了1和本身之外没有其他约数的数,如2和11都是质数,而6不是 ...
- JSONP跨域和CORS跨域的区别
跨域: 由于浏览器中的javascript的同源策略,同源策略会阻止一个域的JavaScript脚本和另一个域的内容进行交互. 同源:协议,域名,端口,三者有一个不同即为跨域. 解决跨域有以下多种方法 ...
- SpringBoot整合Redis、mybatis实战,封装RedisUtils工具类,redis缓存mybatis数据 附源码
创建SpringBoot项目 在线创建方式 网址:https://start.spring.io/ 然后创建Controller.Mapper.Service包 SpringBoot整合Redis 引 ...
- Galera Cluster for MySQL 集群恢复
node1: 1.rm -rf grastate.dat 2.mysqld_safe --wsrep-recover 3.galera_new_cluster node2: systemctl res ...
- Word Count(C语言)
1.项目地址 https://github.com/namoyuwen/word-count 2.项目相关要求 2.1 项目描述 Word Count 1. 实现一个简单而完整的软件工具(源程序 ...
- Rethinking the performance comparison between SNNS and ANNS
郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! Abstract ANN是通向AI的一种流行方法,它已经通过成熟的模型,各种基准,开源数据集和强大的计算平台获得了非凡的成功.SNN是一类 ...
- ACM study day3
今天练了二分和快速幂,题目挺难的,挑几个我做上的说一下吧. 先给出几个二分和快速幂的模板函数: 二分 void BS(int m) { int x=,y=a[m-]-a[]; while(y-x> ...
- JS事件——添加、移除事件
element.addEventListener(event, function, useCapture) 方法用于向指定元素添加事件句柄. event: 必须.字符串,指定事件名.注意: 例 使 ...