学习笔记(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问题。
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