Java-CAS 与原子类
CAS(Compare and Swap),即比较并替换,实现并发算法时常用到的一种技术。
CAS 的思想很简单:三个参数,一个当前内存值 V、旧的预期值 A、即将更新的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。
和 CAS 相关的一个概念是原子操作。原子操作是不可被中断的一个或一系列操作。而 CAS 则是 Java 中保证原子操作的一种方式。
从 Java1.5 开始,JDK 的并发包里就提供了一些类来支持原子操作,都是以 Atomic 开头。
volatile 不能保证类似 i++ 这样操作的原子性,CAS 能够保证。
一、原子类使用
以 AtomicInteger 为例,常用 API:
- public final int get():获取当前的值
- public final int getAndSet(int newValue):获取当前的值,并设置新的值
- public final int getAndIncrement():获取当前的值,并自增
- public final int getAndDecrement():获取当前的值,并自减
- public final int getAndAdd(int delta):获取当前的值,并加上预期的值
相比 Integer 的优势,多线程中让变量自增:
private volatile int count = 0;
// 若要线程安全执行执行 count++,需要加锁
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
使用 AtomicInteger 后:
private AtomicInteger count = new AtomicInteger();
public void increment() {
count.incrementAndGet();
}
// 使用 AtomicInteger 后,不需要加锁,也可以实现线程安全
public int getCount() {
return count.get();
}
二、CAS 问题
CAS 方式为乐观锁,synchronized 为悲观锁。因此使用 CAS 解决并发问题通常情况下性能更优。
但使用 CAS 方式也会有几个问题:
1.循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
2.只能保证一个共享变量的原子操作
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就需要用锁。
从 Java 1.5 开始,JDK 提供了 AtomicReference 类来保证引用对象之间的原子性。
3.ABA 问题
如果一个值原来是 A,变成了 B,又变成了 A,那么使用 CAS 进行检查时则会发现它的值没有发生变化,但是实际上却变化了。
解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么 A->B->A 就会变成 1A->2B->3A。
从 Java 1.5 开始,JDK 提供了 AtomicStampedReference、AtomicMarkableReference 类来解决 ABA 问题。
三、CAS 实现
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
public native int getIntVolatile(Object o, long offset);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
最后调用的是 Unsafe 类的方法,主要是 compareAndSwapInt 方法,即 CAS。
查看 Unsafe 的实现:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
其中核心方法为 Atomic::cmpxchg(x, addr, e),其中参数 x 是即将更新的值,参数 e 是原内存的值,参数 addr 为内存地址。
查看 Atomic 的实现:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/share/vm/runtime/atomic.cpp
#include "runtime/atomic.inline.hpp"
jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value) {
assert(sizeof(jbyte) == , "assumption.");
uintptr_t dest_addr = (uintptr_t)dest;
uintptr_t offset = dest_addr % sizeof(jint);
volatile jint* dest_int = (volatile jint*)(dest_addr - offset);
jint cur = *dest_int;
jbyte* cur_as_bytes = (jbyte*)(&cur);
jint new_val = cur;
jbyte* new_val_as_bytes = (jbyte*)(&new_val);
new_val_as_bytes[offset] = exchange_value;
while (cur_as_bytes[offset] == compare_value) {
jint res = cmpxchg(new_val, dest_int, cur);
if (res == cur) break;
cur = res;
new_val = cur;
new_val_as_bytes[offset] = exchange_value;
}
return cur_as_bytes[offset];
}
查看 atomic_linux_x86.inline.hpp,不同系统、不同 CPU 有不同的实现:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/share/vm/runtime/atomic.inline.hpp
Linux 的 x86 实现:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/os_cpu/linux_x86/vm/atomic_linux_x86.inline.hpp
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
Windows 的 x86 实现:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/fea2c7f50ce8/src/os_cpu/windows_x86/vm/atomic_windows_x86.inline.hpp
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0 \
__asm je L0 \
__asm _emit 0xF0 \
__asm L0:
https://www.cnblogs.com/noKing/p/9094983.html
Java-CAS 与原子类的更多相关文章
- Java多线程系列——原子类的实现(CAS算法)
1.什么是CAS? CAS:Compare and Swap,即比较再交换. jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronou ...
- Java线程--Atomic原子类使用
原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11871241.html Java线程--Atomic原子类使用 package concurr ...
- Java多线程—JUC原子类
根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类. 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;2. 数组类型: AtomicIn ...
- 深入浅出Java并发包—原子类操作
我们知道,JDK1.5以后引入了并发包(java.util.concurrent)用于解决多CPU时代的并发问题,而并发包中的类大部分是基于Queue的并发类,Queue在大多数情况下使用了原子类(A ...
- java多线程系列 JUC原子类 CAS及原子类
根据数据类型,可以将JUC包中的原子操作类可以分为4类. 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;2. 数组类型: AtomicInteg ...
- java的原子类到底是啥?ABA,CAS又是些什么?
1)解决并发不是用锁就能解决吗,那SDK干嘛还要搞个原子类出来? 锁虽然能解决,但是加锁解锁始终还是对性能是有影响的,并且使用不当可能会造成死锁之类的问题. 2)原子类是怎样使用的,比如说我要实现一个 ...
- Java并发—原子类,java.util.concurrent.atomic包(转载)
原子类 Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中 的原子操作类提供了一种用法简单.性能高效.线程安全地更新一个变量 ...
- 【Java并发工具类】原子类
前言 为保证计数器中count=+1的原子性,我们在前面使用的都是synchronized互斥锁方案,加锁独占访问的方式未免太过霸道,于是我们来介绍另一种解决原子性问题的无锁方案:原子变量.在正式介绍 ...
- Java原子类中CAS的底层实现
Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...
- Java CAS同步机制 原理详解(为什么并发环境下的COUNT自增操作不安全): Atomic原子类底层用的不是传统意义的锁机制,而是无锁化的CAS机制,通过CAS机制保证多线程修改一个数值的安全性。
精彩理解: https://www.jianshu.com/p/21be831e851e ; https://blog.csdn.net/heyutao007/article/details/19 ...
随机推荐
- Supervisor进程管理配置使用
Supervisor进程管理 在后台应用中,有时候程序进程会异常中止退出,如果没有一个守护进程去守护这个应用进程我们就需要及时发现并重启进程.如果每一个应用进程都写一个自己的守护进程难免会比较麻烦,而 ...
- java lambda 所有列求和
今天做东西的时候遇到一个需求,求list集合所有列的求和.折腾半天也没有搞出来,网上大部分都是单列求和就像下面这样的,其他都差多,什么 min,max avg count 只得到了number这个属性 ...
- 轻量化模型之MobileNet系列
自 2012 年 AlexNet 以来,卷积神经网络在图像分类.目标检测.语义分割等领域获得广泛应用.随着性能要求越来越高,AlexNet 已经无法满足大家的需求,于是乎各路大牛纷纷提出性能更优越的 ...
- 去掉行尾的^M
1. 处理掉行尾的^M 在windos下进行linux内核驱动编写,调试成功后需要集成到内核代码中去,所以会通过虚拟机共享文件夹拷贝到内核对应目录,这时候看源码文件还是没有异常的. 当对该文件进行回车 ...
- D. Happy Tree Party CodeForces 593D【树链剖分,树边权转点权】
Codeforces Round #329 (Div. 2) D. Happy Tree Party time limit per test 3 seconds memory limit per te ...
- shell脚本基础编写
shell脚本的格式 名称:Shell 脚本文件的名称可以任意,但为了避免被误以为是普通文件,建议将 .sh 后缀加上,以表示是一个脚本文件. shell 脚本中一般会出现三种不同的元素: 第一行的脚 ...
- linux下top命令的使用
top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器 视图参数含义 top视图分为两部分:操作系统资源概况信息和进程信息.首先分析资源 ...
- Java 扫描微信公众号二维码,关注并自动登录网站
https://blog.csdn.net/qq_42851002/article/details/81327770 场景:用户扫描微信公众号的二维码,关注后自动登录网站,若已关注则直接登录. 逻辑: ...
- 扫描局域网ip存活
#!/bin/bash # #******************************************************************** #encoding -*-utf ...
- Redis之哨兵机制(五)
什么是哨兵机制 Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务: · 监控(Monitoring): 哨兵(sentinel) 会不断 ...