1 CAS原理
CAS是所有原子类的底层原理,乐观锁主要采用CAS算法。
CAS,比较并交换,是JDK提供的非阻塞原子性操作,通过硬件保证比较-更新操作的原子性。 通常结合volatile保证共享变量的原子性。
思想:获取当前变量最新值A(预期值),然后进行CAS操作。此时如果内存中变量的值V(内存值V)等于预期值A,说明没有被其他线程修改过,我就把变量值改成B(更新值);如果不是A,便不再修改。
CAS操作利用CPU的特殊指令,由CPU保证原子性,完成一系列操作,不存在安全性问题。
CAS的变量需要用volatile修饰,以便在各线程之间保证可见。
CAS算法思想的使用场景
- 乐观锁
- 并发容器,例如ConcurrentHashMap
- 原子类
2 AtomicLong中CAS使用分析
代码语言:javascript复制// 获取Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 获取变量value在内存中的偏移量
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) {
throw new Error(ex); }
}
类加载过程中首先会加载Unsafe实例和valueOffset偏移量。valueOffset 表示变量在内存中的偏移地址,Unsafe根据内存偏移地址获取数据的预期值,然后进行CAS操作。为保证获取到值是最新值,因此变量通常用volatile修饰。
代码语言:javascript复制public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
// Unsafe类
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
// 通过对象地址和偏移量获取变量的最新值
var6 = this.getLongVolatile(var1, var2);
// 满足条件进行CAS操作
} while(!this.compareAndSwapLong(var1, var2, var6, var6 var4));
return var6;
}
CAS方法底层c 源码实现
代码语言:javascript复制UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapLong(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jlong e, jlong x))
UnsafeWrapper("Unsafe_CompareAndSwapLong");
Handle p (THREAD, JNIHandles::resolve(obj));
// 内存地址
jlong* addr = (jlong*)(index_oop_from_field_offset_long(p(), offset));
if (VM_Version::supports_cx8())
// Atomic::cmpxchg原子性比较和替换
return (jlong)(Atomic::cmpxchg(x, addr, e)) == e;
else {
jboolean success = false;
ObjectLocker ol(p, THREAD);
if (*addr == e) { *addr = x; success = true; }
return success;
}
UNSAFE_END
3 CAS的缺点
3.1 ABA问题
CAS是比较值,如果值相等则变换。此处可能存在这样的情况,线程1获取变量值为5,线程2将值改为10,线程3再将值改回5。对于线程1,变量的值没有变,但对于计数等后续操作是不正确的。
分析:ABA问题的产生是因为变量的状态值产生了环形变换。如果变量的值只能朝一个方向转化,便不会构成环形,不存在ABA问题。
解决方法:可以参考数据库乐观锁的处理,加版本号,变量更新时版本号会改变。通过比较版本号代替比较变量值。与集合的Fast-Fail机制类似,检查modCount值是否一致。
3.2 自旋时间长带来性能消耗
以AtomicLong为例,高并发场景下,如果线程一直无法进行CAS操作,内部是dowhile死循环,会一直自旋,消耗CPU。
自旋的理解
自旋是重试策略,既可以是乐观锁的重试策略,也可以使悲观锁的重试策略。例如AtimicLong很多方法都调用了getAndAddLong方法,内部便是利用了自旋 CAS操作。至于悲观锁,ReentrantLock的超时获取锁方法tryLock,便利用了for循环。
4 Unsafe
Unsafe是CAS核心类。Java无法直接访问底层操作系统,而是通过本地方法访问。JDK中Unsafe类,底层调用本地方法,提供硬件级别原子操作。
Unsafe类属于rt.jar包,使用bootstrap类加载器加载,而普通main函数类是使用AppClassLoader加载。Unsafe直接操作内存,因此叫不安全类,不能随意调用。
可以通过反射获取Unsafe实例。
代码语言:javascript复制Field field = Unsafe.class.getDeclareField("theUnsafe");
field.setAccessible(true);
本文案例代码位置:https://gitee.com/dtyytop/advanced-java
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/181293.html原文链接:https://javaforall.cn