CAS概念
CAS(Compare And Swap):比较并交换,CAS是一种原子操作,针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给这个变量赋一个新值。CAS可以看做是乐观锁的一种实现,Java原子类中的递增操作就是通过CAS自旋实现的。CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。
CAS应用
在 Java 中,CAS 操作是由 Unsafe 类提供支持的,该类定义了三种针对不同类型变量的 CAS 操作,如图:
它们都是 native 方法,由 Java 虚拟机提供具体实现,这意味着不同的 Java 虚拟机对它们的实现可能会略有不同。以compareAndSwapInt 为例,Unsafe 的 compareAndSwapInt方法接收 4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值。该方法会针对指定对象实例中的相应偏移量的字段执行CAS 操作。
package com.warrior.juc.atomic;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @Author warrior
* CAS原子性操作案例
*/
public class CASDemo {
public static void main(String[] args) {
Entity entity = new Entity();
Unsafe unsafe = UnsafeFactory.getUnsafe();
long offset = UnsafeFactory.getFieldOffset(unsafe, Entity.class, "x");
boolean successful;
// 4个参数分别是:对象实例、字段的内存偏移量、字段期望值、字段新值
successful = unsafe.compareAndSwapInt(entity, offset, 0, 3);
System.out.println(successful + "\t" + entity.x);
successful = unsafe.compareAndSwapInt(entity, offset, 3, 5);
System.out.println(successful + "\t" + entity.x);
successful = unsafe.compareAndSwapInt(entity, offset, 3, 8);
System.out.println(successful + "\t" + entity.x);
}
static class UnsafeFactory {
/**
* 获取 Unsafe 对象
*
* @return
*/
public static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取字段的内存偏移量
*
* @param unsafe
* @param clazz
* @param fieldName
* @return
*/
public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {
try {
return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
}
static class Entity {
int x;
}
}
CAS缺陷
CSA虽然高效的解决了原子操作,但是还是存证一些缺陷的,主要表现在三个方面:
自旋CAS长时间地不成功,则会给CPU带来非常大的开销。
只能保证一个共享变量的原子操作
ABA问题
ABA
ABA问题
当有多个线程对一个原子类进行操作的时候,某个线程在短暂的时间内将原子类的值A修改为B,又马上将其修改为A,此时其他线程不感知,还是会修改成功。
package com.warrior.juc.atomic;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author warrior
* ABA问题案例
*/
public class ABADemo {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) {
new Thread(() -> {
int value = atomicInteger.get();
System.out.println(Thread.currentThread().getName() + " read value:" + value);
//休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//CAS修改value值
boolean success = atomicInteger.compareAndSet(value,3);
if(success){
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 3 success");
}else {
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 3 fail");
}
}, "Thread-1").start();
new Thread(() -> {
int value = atomicInteger.get();
System.out.println(Thread.currentThread().getName() + " read value:" + value);
//CAS修改value值
boolean success = atomicInteger.compareAndSet(value,2);
if(success){
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 2 success");
value = atomicInteger.get();
System.out.println(Thread.currentThread().getName() + " read value:" + value);
success = atomicInteger.compareAndSet(value,1);
if(success){
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 1 success");
}
}
},"Thread-2").start();
}
}
输出结果:
Thread-1 read value:1
Thread-2 read value:1
Thread-2 update from 1 to 2 success
Thread-2 read value:2
Thread-2 update from 2 to 1 success
Thread-1 update from 1 to 3 success
ABA解决方案
数据库有个锁称为乐观锁,是一种基于数据库版本实现的数据同步机制,每次修改一次数据,版本就会进行累加。同样,Java也提供了相应的原子引用类AtomicStampedReference<V>
reference即我们实际存储的变量,stamp是版本,每次修改可以通过+1保证版本唯一性。这样就可以保证每次修改后的版本也会往上递增。
package com.warrior.juc.atomic;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @Author warrior
* ABA问题解决方案案例
*/
public class AtomicStampedReferenceDemo {
// 定义AtomicStampedReference Pair.reference值为1, Pair.stamp为1
static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1,1);
public static void main(String[] args) {
new Thread(() -> {
int[] stampHolder = new int[1];
int value = (int) atomicStampedReference.get(stampHolder);
int stamp = stampHolder[0];
System.out.println(Thread.currentThread().getName() + " read value:" + value);
//休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//CAS修改value值
boolean success = atomicStampedReference.compareAndSet(value,3,stamp,stamp+1);
if(success){
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 3 success");
}else {
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 3 fail");
}
}, "Thread-1").start();
new Thread(() -> {
int[] stampHolder = new int[1];
int value = (int) atomicStampedReference.get(stampHolder);
int stamp = stampHolder[0];
System.out.println(Thread.currentThread().getName() + " read value:" + value);
//CAS修改value值
boolean success = atomicStampedReference.compareAndSet(value,2,stamp,stamp+1);
if(success){
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 2 success");
value = (int) atomicStampedReference.get(stampHolder);
stamp = stampHolder[0];
System.out.println(Thread.currentThread().getName() + " read value:" + value);
success = atomicStampedReference.compareAndSet(value,1,stamp,stamp+1);
if(success){
System.out.println(Thread.currentThread().getName() + " update from "+ value + " to 1 success");
}
}
},"Thread-2").start();
}
}
输出结果:
Thread-1 read value:1
Thread-2 read value:1
Thread-2 update from 1 to 2 success
Thread-2 read value:2
Thread-2 update from 2 to 1 success
Thread-1 update from 1 to 3 fail
AtomicMarkableReference可以理解为上面AtomicStampedReference的简化版,就是不关心修改过几次,仅仅关心是否修改过。因此变量mark是boolean类型,仅记录值是否有过修改