文章目录
AtomicInteger
AtomicInteger是使用CAS来保持原子性。而Unsafe是CAS的核心类,由于Java方法无法直接访问操作系统低层,需要通过本地native方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据
Unsafe
// 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); }
}
//用volatile修饰value值,保持可见性
private volatile int value;
Unsafe类中的方法大多数都被native修饰,也就是说Unsafe类直接调用操作系统的底层资源去执行相应任务。
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
//变量,内存偏移量,加1
return unsafe.getAndAddInt(this, valueOffset, 1);
}
CAS(Compare-And-Swap)一条cpu并发原语
判断内存中某个位置的值是否为预期值,如果是则更改为新值,这个过程是原子的。
JVM会帮我们实现出CAS汇编指令,完全依赖于硬件的功能,通过它实现了原子操作。
CAS是一种系统原语,由若干条指令组成。且原语的执行必须是连续的,执行过程中不允许被打断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题
//对象,内存偏移量,增加量
public final int getAndAddInt(Object var1, long var2, int var4) {
//var5存储的是期望值
int var5;
//自旋锁基本思想
do {
//获得主物理内存中的值
var5 = this.getIntVolatile(var1, var2);
}//比较并交换
while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
//返回修改后的值
return var5;
}
CAS缺点
- 在多线程下,可能有的线程倒霉一直有别的线程去修改值,导致长时间循环,CPU浪费
- 只能解决一个变量的线程安全问题
- ABA问题
ABA问题
假设有三个线程,线程A、B、C,同时去修改变量d=1,A想把d修改为2,在此之前B把d修改为了3,C把d又修改成了1,则A去修改d的时候没有发现变量d被修改过,直接修改。
CAS不在乎中间发生了什么,只在乎在作比较的时候值是否相等。中间过程容易出现问题
解决ABA问题
原子引用AtomicReference
class User {
String userName;
int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("z3", 114);
User l4 = new User("l4", 116);
AtomicReference<User> userAtomicReference = new AtomicReference<>();
userAtomicReference.set(z3);
//第一次修改引用,成功
System.out.println(userAtomicReference.compareAndSet(z3, l4)+"\t "+userAtomicReference.get());
//第二次修改引用,失败
System.out.println(userAtomicReference.compareAndSet(z3, l4)+"\t "+userAtomicReference.get());
}
}
ABA问题,在做每次修改的时候,在数据后加上一个版本号,每修改一次,版本号加一。
AtomicStampedReference
利用版本号(时间戳)来保证解决ABA的问题,也就是说保证每一行的数据都是保证唯一的。
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, 1, 2);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, 2, 3);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());
}, "t3").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + atomicStampedReference.getStamp());
try {
//暂停t4 3s
//保证t1完成一次ABA
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(100, 400, 1, 2) +
"\t当前版本号:" + atomicStampedReference.getStamp() + "\t当前最新值:" + atomicStampedReference.getReference());
}, "t4").start();