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

【学习笔记 time: 2022-03-31】 CAS底层原理分析(AtomicInteger,atomicStampedReference使用)_版本号


执行结果

执行结果:  true
比较交换结果: 2022
执行结果1:  false
比较交换结果1: 2022
-----------------------------------
执行结果2:  false
比较交换结果2:  5

CAS底线原理(重点

悬念:为什么多线程用CAS而不用synchronized,怎么保持原子一致性?怎么保持线程安全?

  1. 分析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);
  1. 分析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);
  1. Unsafe
    Unsafe是JDK娘胎里带出来的,在下面这个路径

    Unsafe是CAS核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作内存数据,类似C语言指针直接操作内存。所以Unsafe直接调用底层数据执行相应任务。
  2. CAS核心原理
    CAS(compare-and-swap) 它是一条CPU并发原语
    他的功能是判断内存某个未知的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
    JVM会帮我们实现CAS的汇编指令。这是一种完全依赖于硬件的功能,通过他实现原子操作。重点:原语的执行必须是连续的,在执行过程中不允许中断,也就是CAS是一条CPU原子指令,不会造成所谓的数据不一致问题(线程安全)。
  3. 多线程下CAS与synchronized使用比较
    CAS不需要上锁就能保证数据一致性问题,但底层需要多次循环

CAS 缺点

  1. weakCompareAndSetInt一直比较不成功(返回false),会一直尝试,造成自旋,循环时间长造成CPU开销大。
  2. 只能保证一个共享变量的原子性。而synchronized可以保证很多
    面试流程

ABA问题

  1. 简述 在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();}
 }
  1. 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