JUC原子类
CAS
实现线程安全的方法:
- 互斥同步:
synchronized
,Lock
. - 非阻塞同步:CAS,Atomic.
- 无同步方法:栈封闭,ThreadLocal,可重入代码
CAS介绍
- CAS(Compare-And-Swap):对比并交换,是一个原子操作.
- 先比较旧值是否发生变化,若没有,则交换成新值;否则不进行交换.
- CAS是原子的,多线程使用CAS更新数据时,可以不使用锁.
示例:
public AtomicInteger i = new AtomicInteger(0);
public int add(){
return i.addAndGet(1);
}
存在的问题:
- ABA问题:CAS只检查值是否发生变化,若一个值由A变为B后又变回为A,则认为没有变化而正常进行操作.
- 循环时间长开销大:自旋CAS若长时间不成功,则会给CPU带来很大的执行开销.
- 只能保证一个共享变量的原子操作:当对一个共享变量进行操作时,可以使用CAS保证原子操作.但是要需要对多个共享变量进行操作,则无法保证多个变量的原子性.
解决:
- ABA问题:使用版本号,在变量上加入版本号,每次变量变更后更新对应的版本号,若版本号一致才认为没有改变.(JUC提供
AtomicStampedReference
,其先检查当前引用是否等于预期引用,若等于才设置为新值) - 自旋问题:使用处理器提供的pause指令==>延迟流水线执行指令,避免循环时因内存顺序冲突引起的流水线被清空.
- 多变量问题:使用
AtomicReference
类将多个变量放在一个对象中进行CAS操作.
UnSafe类
提供的功能:
- 内存操作
- CAS
- Class相关
- 对象操作
- 线程调度
- 系统信息获取
- 内存屏障
- 数组操作
CAS原理:使用自旋调用UnSafe中CAS更新,若失败则重试.
AtomicInteger
常用API
public final int get(); // 获取值
public final int getAndSet(int newValue); // 获取当前值,并设为新值
public final int getAndIncrement(); // 获取旧值,并自增
public final int getAndDecrement(); // 获取旧值,并自增
public final int getAndAdd(int delta); // 获取旧值,并加指定值
void lazySet(int newValue); // 设为指定值,但非即时
原理:使用volatile
和CAS保证原子操作.
- volatile:保证可见性,多线程并发时,一个线程修改,其他线程可见.
- CAS:保证原子性.
原子类小结
原子更新基本类型
AtomicBoolean
:原子更新布尔类型.AtomicInteger
:原子更新整型.AtomicLong
:原子更新长整型.
原子更新数组
AtomicIntegerArray
:原子更新整型数组.AtomicLongArray
:原子更新长整型数组.AtomicReferenceArray
:原子更新引用类型数组.get(int index)
:获取指定位置上的元素.compareAndSet(int i,E except,E update)
:若当前值等于预期值,则原子方式设置为新值.
原子更新引用类型
AtomicReference
:原子更新引用类型.AtomicStampedReference
:原子更新引用类型,使用Pair存储元素值和版本号.AtomicMarkableReference
:原子更新带有标记的引用类型.
原子更新字段类
AtomicIntegerFieldUpdater
:原子更新整型的字段的更新器.AtomicLongFieldUpdater
:原子更新长整型的字段的更新器.AtomicReferenceFieldUpdater
:原子更新引用类型字段的更新器.
示例:
// 1. 由AtomicIntegerFieldUpdater的newUpdater获取更新器(传入对应的类和待更新的字段名)
// 2. 使用getAndAdd方法,传入待更新的对象实例和更新值
// 注: 更新的字段类型要和更新器一致,否则抛出异常
// 字段必须是volatile,保证线程间的可见性
// 字段的修饰符要和操作对象的字段类型一致
// 只能操作实例对象的字段,不能操作static字段
// 不能修改final字段,不可对不可变字段更新
// Integer/Long包装类要使用AtomicReferenceUpdater进行修改
public class Test{
public void test(){
newClass nc = new newClass();
AtomicIntegerFieldUpdater<newClass> updater1 = AtomicIntegerFieldUpdater.newUpdater(newClass.class, "publicVal");
int res1 = updater1.getAndAdd(nc, 2);
System.out.println(res1);
System.out.println(nc.publicVal);
// 更新器类型与更新字段类型不一致,抛出异常
AtomicIntegerFieldUpdater<newClass> updater2 = AtomicIntegerFieldUpdater.newUpdater(newClass.class, "integerVal");
int res2 = updater2.getAndAdd(nc, 1);
System.out.println(res2);
System.out.println(nc.integerVal);
}
}
class newClass{
public volatile int publicVal = 1;
protected volatile int protectedVal = 2;
private volatile int privateVal = 3;
public volatile static int staticVal = 4;
public volatile Integer integerVal = 5;
public volatile Long longVal = 6l;
}
AtomicStampedReference原理
解决ABA问题
静态私有类Pair
包含两个域:
reference
:维护对象引用.stamp
:标志版本.
更新流程:
- 若元素值和版本号没有改变,且更新值不变,返回true.
- 若元素值和版本号未变,而更新值不同,则构造新的Pair对象并进行CAS更新Pair.
小结:
- 使用版本号控制.
- 不重复使用Pair的引用,每次新建Pair作为CAS对象.
示例:
private static AtomicStampedReference stamp = new AtomicStampedReference(0,0); // 初始的reference和stamp
@Test
public void test() throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isSuccess = stamp.compareAndSet(0, 1, 0, 1);
System.out.println(isSuccess);
});
// 线程2的stamp不匹配,无法设置成功
Thread t2 = new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean isSuccess = stamp.compareAndSet(0, 2, 1, 2); // 若设为(0,2,0,2)则有可能更新成功
System.out.println(isSuccess);
});
t1.start();
t2.start();
TimeUnit.SECONDS.sleep(1);
System.out.println("reference is: " + stamp.getReference() + " stamp: "+stamp.getStamp());
}
参考: