CAS 你知道吗:
compare and swap 比较并交换
比较当前工作内存中的值和主内存的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。
他是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
CAS并发原语体现在JAVA中就是sun,misc.Unsafe类中的各个方法,调用Unsafe类的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作。
CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条原子指令,不会造成数据不一致问题。
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(7);
System.out.println(atomicInteger.compareAndSet(7, 1000) + "---" + atomicInteger.get());
//true---1000
System.out.println(atomicInteger.compareAndSet(7, 2000) + "---" + atomicInteger.get());
//false---1000
如果线程的期望值和物理内存的真实值一样,就修改,
如果不一样,本次修改失败。
}
CAS底层原理?如果知道,谈谈你对Unsafe的理解:
靠unsafe类的CPU指令原语保证原子性
//java.util.concurrent.atomic.AtomicInteger
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
this -> 当前对象
valueOffset -> 内存偏移量 即内存地址
valueOffset 表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的
变量使用volatile修饰,保证了多线程之间的内存可见性
什么是Unsafe类:
Unsafe是java的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
Unsafe类的大量方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。
//sun.misc.Unsafe
public native int getIntVolatile(Object var1, long var2);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//var5 = 当前这个对象这个地址上的值是多少
var5 = this.getIntVolatile(var1, var2);
//如果当前对象这个地址的值和上面获取的var5一样,就加var4
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//修改成功,返回true,跳出循环
//判断这个值被别人改过了,不是我们获取的期望值了,返回false,继续循环,再获取最新值,直到比较成功
return var5;
}
CAS缺点:
循环开销大
- 如果CAS一直不成功,可能会给CPU带来很大的开销
只能够保证一个共享变量的原子操作
引出来ABA问题
原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗:
CAS会导致 “ABA问题”。
CAS算法实现的一个重要前提是要去除主内存中某时刻的数据并在当下时刻进行比较替换,那么这个时间差会导致数据的变化
比如一个线程A从内存V位置取出数据X,这时候另一个线程B也从内存中取出X,并且线程B进行了一些操作将值变成了Y,然后线程B又将V位置的数据变成X,这时候线程A进行CAS操作发现内存中数据仍是X,然后A线程操作成功。
尽管线程A的CAS操作成功,但是不代表这个过程就是没问题的
原子引用 AtomicReference<V>
:
@Test
public void atomicReference() {
User user1 = new User(15, "王腾");
User user2 = new User(18, "皇城");
User user3 = new User(20, "王藤");
AtomicReference<User> userAtomicReference = new AtomicReference<>();
userAtomicReference.set(user1);
System.out.println(userAtomicReference.compareAndSet(user1, user2) + "\t current value: " + userAtomicReference.get());
// true current value: User{age=18, name='皇城'}
System.out.println(userAtomicReference.compareAndSet(user1, user3) + "\t current value: " + userAtomicReference.get());
// false current value: User{age=18, name='皇城'}
}
解决ABA问题,时间戳原子引用AtomicStampedReference<T>:
public static void main(String[] args) {
System.out.println("=============ABA问题产生================");
AtomicReference<Integer> atomicReference = new AtomicReference<>(1);
new Thread(() -> {
atomicReference.compareAndSet(1, 2);
atomicReference.compareAndSet(2, 1);
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(atomicReference.compareAndSet(1, 3) + "\t" + "current-value: " + atomicReference.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=============ABA问题解决================");
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 0);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "第一次获得版本号:" + atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行结果" + atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1) + "\t 当前版本号" + atomicStampedReference.getStamp()+ "\t" + "current-value: " + atomicStampedReference.getReference());
System.out.println(Thread.currentThread().getName() + "执行结果" + atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1) + "\t 当前版本号" + atomicStampedReference.getStamp()+ "\t" + "current-value: " + atomicStampedReference.getReference());
}, "t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "第一次获得版本号:" + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行结果" + atomicStampedReference.compareAndSet(100, 3000, stamp, stamp + 1) + "\t 当前版本号" + atomicStampedReference.getStamp()+ "\t" + "current-value: " + atomicStampedReference.getReference());
}, "t4").start();
}
=============ABA问题产生================
true current-value: 3
=============ABA问题解决================
t3第一次获得版本号:0
t4第一次获得版本号:0
t3执行结果true 当前版本号1 current-value: 101
t3执行结果true 当前版本号2 current-value: 100
t4执行结果false 当前版本号2 current-value: 100
//ps:在做上面的实验的时候,原来写的是
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1000, 0);
//然后compareAndSet的时候全部都是false:
atomicStampedReference.compareAndSet(1000, 2000, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)
//如果用String的话是true的:
AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<String>("1000", 1);
atomicStampedReference.compareAndSet("1000", "2000", atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
//找到问题是Integer的装箱拆箱问题,会变成两个对象,所以compare数据失败了
//改为 [-127, 128]的数就为true了
通过源码可以看到,在 Interger 的 valueOf 方法中,如果数值在 [-127, 128] 范围的时候,就会去 IntegerCache.cache 这个数组寻找有没有存在的 Integer 对象的引用,如果有,则直接返回对象的引用,如果没有(超过了范围),就新建一个 Integer 对象
————————————————
原文链接:https://blog.csdn.net/babycan5/article/details/81942230