学习笔记(CAS,AtomicInteger,synchronized)
CAS是什么?
简简单单一个词:比较并交换(compareAndSwap),volatile关键字的底层也使用到了
CAS的作用?
在多线程操作一个主物理内存的情况下保持数据的一致性
简单Demo
代码
package com.zhuyh.springcloud;
import java.util.concurrent.atomic.AtomicInteger;
public class test {
public static void main(String[] args) {
AtomicInteger atomicInteger1 = new AtomicInteger(5);
boolean b = atomicInteger1.compareAndSet(5, 2022);
boolean b1 = atomicInteger1.compareAndSet(5, 2022);
System.out.println("执行结果: " + b);
System.out.println("比较交换结果: " + atomicInteger1.get());
System.out.println("执行结果1: " + b1);
System.out.println("比较交换结果1: " + atomicInteger1.get());
System.out.println("-----------------------------------");
AtomicInteger atomicInteger2 = new AtomicInteger(5);
boolean b2 = atomicInteger2.compareAndSet(6, 2022);
System.out.println("执行结果2: " + b2);
System.out.println("比较交换结果2: " + atomicInteger2.get());
}
}
第一个false的原因是已经被第一次比较并交换将值改成了2022,导致这一次的期望值(快照值/副本值)与主物理内存的真实值不一样,所以compareAndSwap结果为false。
执行结果
执行结果: true
比较交换结果: 2022
执行结果1: false
比较交换结果1: 2022
-----------------------------------
执行结果2: false
比较交换结果2: 5
CAS底线原理(重点)
悬念:为什么多线程用CAS而不用synchronized,怎么保持原子一致性?怎么保持线程安全?
- 分析atomicInteger.getAndIncrement();方法
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
public final int getAndIncrement() {
// this - 当前对象 VALUE - 内存偏移量(内存地址) 1 - 加一
return U.getAndAddInt(this, VALUE, 1);
}
U.objectFieldOffset(AtomicInteger.class, “value”);
public long objectFieldOffset(Class<?> c, String name) {
if (c == null || name == null) {
throw new NullPointerException();
}
return objectFieldOffset1(c, name);
}
objectFieldOffset1(c, name);
native 表示调用到底层了,这里获取内存地址
private native long objectFieldOffset1(Class<?> c, String name);
- 分析U.getAndAddInt(this, VALUE, 1);方法
@HotSpotIntrinsicCandidate
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
// 获取当前对象地址上的值(从主物理内存取)
v = getIntVolatile(o, offset);
// !weakCompareAndSetInt(o, offset, v, v + delta)
// 用快照值和主物理内存值进行比较,值没变为!true 则return v,反之接着do,取出再进行比,依次这样,直至成功为止。
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
@HotSpotIntrinsicCandidate
// o - 当前对象 offset - 当前地址 expected - 期望值 x - 增量
public final boolean weakCompareAndSetInt(Object o, long offset,
int expected,
int x) {
return compareAndSetInt(o, offset, expected, x);
}
// native 直接调用底层资源进行compareAndSetInt
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset,
int expected,
int x);
- Unsafe
Unsafe是JDK娘胎里带出来的,在下面这个路径
Unsafe是CAS核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作内存数据,类似C语言指针直接操作内存。所以Unsafe直接调用底层数据执行相应任务。 - CAS核心原理
CAS(compare-and-swap) 它是一条CPU并发原语
他的功能是判断内存某个未知的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
JVM会帮我们实现CAS的汇编指令。这是一种完全依赖于硬件的功能,通过他实现原子操作。重点:原语的执行必须是连续的,在执行过程中不允许中断,也就是CAS是一条CPU原子指令,不会造成所谓的数据不一致问题(线程安全)。 - 多线程下CAS与synchronized使用比较
CAS不需要上锁就能保证数据一致性问题,但底层需要多次循环
CAS 缺点
- weakCompareAndSetInt一直比较不成功(返回false),会一直尝试,造成自旋,循环时间长造成CPU开销大。
- 只能保证一个共享变量的原子性。而synchronized可以保证很多
面试流程
ABA问题
- 简述 在A,B两个线程同时访问主物理内存时,假设A的处理时间比B慢很多,两个线程最开始取到的内存是一样的,因为B的速度快很多,B对内存进行了多次操作,将内存修改很多次,但最后又改为了原始数据,此时A再去做CAS发现没有问题,但其实B进行了很多次操作,这就是ABA问题。
1. public class test {
static AtomicReference atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, “t1”).start();new Thread(() -> {
// 线程暂停一秒钟,保证t1先执行
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());
}, "t2").start();}
}
- ABA问题解决
- 原子引用(AtomicReference)
package com.zhuyh.springcloud;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.concurrent.atomic.AtomicReference;
@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
private Integer id;
private String name;
}
public class test {
public static void main(String[] args) {
User user1 = new User(1, "张三");
User user2 = new User(2, "李四");
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
boolean b1 = atomicReference.compareAndSet(user1, user2);
System.out.println("比较结果:"+b1);
System.out.println("比较交换后:"+atomicReference.get());
System.out.println();
boolean b2 = atomicReference.compareAndSet(user1, user2);
System.out.println("比较结果:"+b2);
System.out.println("比较交换后:"+atomicReference.get());
}
}
比较结果:true
比较交换后:User(id=2, name=李四)
比较结果:false
比较交换后:User(id=2, name=李四)
- 新增版本机制(类似于时间戳)带时间戳的原子引用AtomicStampedReference
package com.zhuyh.springcloud;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class test {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 第一次版本号:" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + " 第二次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + " 第三次版本号:" + atomicStampedReference.getStamp());
System.out.println("________________________________");
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + " 第一次版本号:" + stamp);
try {
// 等上面t3完成一次CAS
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + " update: " + b + "\nlast version: "+atomicStampedReference.getStamp());
System.out.println("last value: "+atomicStampedReference.getReference());
}, "t4").start();
}
}
t3 第一次版本号:1
t4 第一次版本号:1
t3 第二次版本号:2
t3 第三次版本号:3
________________________________
t4 update: false
last version: 3
last value: 100