希望造成叫醒你的是梦想
而不是闹钟
Atomic原子类:
在化学中,原子是构成一般物质的最小单位,是不可分割的。而在这里,Atomic 表示当前操作是不可中断的,即使是在多线程环境下执行,Atomic 类,是具有原子操作特征的类。
类型
基本类型
AtomicInteger:整形原子类
AtomicLong:长整型原子类
AtomicBoolean:布尔型原子类
引用类型
AtomicReference:引用类型原子类
AtomicStampedReference:原子更新引用类型里的字段原子类
AtomicMarkableReference :原子更新带有标记位的引用类型
对象的属性修改类型
AtomicIntegerFieldUpdater:原子更新整形字段的更新器
AtomicLongFieldUpdater:原子更新长整形字段的更新器
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,以及解决使用 CAS 进行原子更新时可能出现的 ABA 问题
常用方法
public final int get(); // 获取当前的值
public final int getAndSet(int newValue); // 获取当前的值,并设置新的值
public final int getAndIncrement(); // 获取当前的值,并自增
public final int getAndDecrement(); // 获取当前的值,并自减
public final int getAndAdd(int delta); // 获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update); // 如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue); // 最终设置为 newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
CAS
CompareAndSwap(比较并交换)
CPU并发原语,原子指令,执行连续,不会造成数据不一致性问题
CAS与Atomic原子类
AtomicInteger 类利用 CAS (Compare and Swap) + volatile + native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
看一下Atomic原子类源码
进入Atomic原子类的Unsafe类的compareAndSet方法
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
UnSafe 类的 objectFieldOffset() 方法是个本地方法,这个方法是用来拿“原值”的内存地址,返回值是 valueOffset;另外,value 是一个 volatile 变量,因此 JVM 总是可以保证任意时刻的任何线程总能拿到该变量的最新值。
public native long objectFieldOffset(Field var1);
进去,compareAndSwapInt是一个native方法,底层直接通过objectFieldOffset拿到的内存地址进行交换
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
可见,通过do while循环中get()方法和updateFunction.applyAsInt(prev)获取比较值和交换值,通过调用native修饰的compareAndSwapInt进行CAS操作,达到修改值的目的
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
自旋锁
do while引出自旋锁,顺便手写一个自旋锁
public class ZiXuan {
//保存线程
static AtomicReference<Thread> atomicReference=new AtomicReference<>();
public static void myLock(){
//获取线程
Thread thread = Thread.currentThread();
//成功则退出循环
while (!atomicReference.compareAndSet(null,thread)){
}
System.out.println(Thread.currentThread().getName()+"锁获取成功!");
}
public static void myUnLock(){
Thread thread = Thread.currentThread();
//成功则退出循环
while (!atomicReference.compareAndSet(thread,null)){
}
System.out.println(Thread.currentThread().getName()+"释放锁成功!");
}
public static void main(String[] args) throws Exception {
new Thread(()->{
//获取锁
System.out.println("已进去线程1");
myLock();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
myUnLock();
}).start();
new Thread(()->{
System.out.println("已进去线程2");
//获取锁
myLock();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
myUnLock();
}).start();
}
}
CAS缺点
● 循环时间长开销大
● 只能保证一个共享变量的原子操作
● ABA问题
简单来说:
一个线程把共享变量A修改为B再改为A,另一个线程看到还是A认为没修改过
解决:
使用带时间戳的原子引用
//参数需要带时间戳
AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(100, 1);
//获取当前时间戳
int stamp = asr.getStamp();
//修改,时间戳不一致修改失败
asr.compareAndSet(100,200,stamp,2);
文章持续更新,可以微信搜索「 绅堂Style 」第一时间阅读,回复【资料】有我准备的面试题笔记。
GitHub https://github.com/dtt11111/Nodes 有总结面试完整考点、资料以及我的系列文章。欢迎Star。