什么是原子类
原子是最小粒度的,操作不可分割的。
原子类作用
对多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后, 新增的原子操作类提供了
一种用法简单、性能高效、线程安全地更新一个变量的方式, 这些类同样位于JUC包下的atomic包下,发展
到JDk1.8,该包下共有17个类, 囊括了原子更新基本类型、原子更新数组、原子更新属性、原子更新引用
jdk1.8新增原子类
DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
到目前为止在java.util.concurrent.atomic
包下提供了17个原子类
可分为4类:基本原子类,数组原子类、引用原子类、字段原子类
①基本原子类
布尔型:AtomicBoolean
整型:AtomicInteger
长整形:AtomicLong
②数组原子类
整型数组:AtomicIntegerArray
长整型数组:AtomicLongArray
引用型数组:AtomicReferenceArray
③引用原子类
AtomicReference:原子更新引用类型
AtomicReferenceFieldUpdater:原子更新引用类型里的字段
AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方 法是AtomicMarkableReference(V initialRef, boolean initialMark)
④字段原子类
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
AtomicLongFieldUpdater:原子更新长整型字段的更新器
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题
基本原子类都是volatile value+cas比较简单略过。
数组原子类以AtomicIntegerArray为例,主要讲述如何根据索引下标定位数组元素?
static {
// 获取此数组的每个成员的内存偏移量(就是每个对象占内存大小),此处整形返回4
int scale = unsafe.arrayIndexScale(int[].class);
if ((scale & (scale - 1)) != 0) // scale必须是2的乘幂
throw new Error("data type scale not a power of two");
// numberOfLeadingZeros返回scale位数第一个遇到1的0的个数
// 0000 0000 0000 0000 0000 0000 0000 0100,此处是29
shift = 31 - Integer.numberOfLeadingZeros(scale);
}
// 下标i的元素内存偏移地址
private static long byteOffset(int i) {
// 由于数组使用的是一块连续的内存空间,可以这么计算
// base = unsafe.arrayBaseOffset(int[].class),获取首个元素的偏移地址
// 可以证明 i * scale + base == i << shift + base
return ((long) i << shift) + base;
}
// 延迟设置,设置的值不保证立刻刷入主内存,被其他线程可见,其它线程可能看到值会在稍许的延迟之后
public final void lazySet(int i, int newValue) {
unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
//使用Unsafe.putOrderedObject方法,这个方法在对低延迟代码是很有用的,它能够实现非堵塞的写入,
//这些写入不会被Java的JIT重新排序指令(instruction reordering),这样它使用快速的存储-存储(store-
//store) barrier, 而不是较慢的存储-加载(store-load) barrier, 后者总是用在volatile的写操作上,
//这种性能提升是有代价的,也就是写后结果并不会被其他线程看到,甚至是自己的线程,通常是几纳秒后
//被其他线程看到,这个时间比较短,所以代价可以忍受。总之牺牲可见性来提升性能。
//关于这一点,可以参考:https://www.cnblogs.com/chenyangyao/p/5269622.html
}
引用原子类,AtomicReference略过,主要介绍AtomicReferenceFieldUpdater和AtomicMarkableReference!
一、AtomicReferenceFieldUpdater特征
①基于反射
②AtomicReferenceFieldUpdater是抽象类,通过静态方法newUpdater(Class<U> tclass, Class<W> vclass, String fieldName)来创建实例对象,实例对象类型是AtomicReferenceFieldUpdater的静态内部类AtomicReferenceFieldUpdaterImpl。其中参数
tclass | 目标对象的类型 |
vclass | 目标字段的类型 |
fieldName | 目标字段名 |
③fieldName必须是volatile,类型修饰符不能是private、static,如果目标对象类型同AtomicReferenceFieldUpdater所在使用类是同包或者目标对象类型在AtomicReferenceFieldUpdater所在使用类中,则fieldName可以是默认修饰符或者protected。不同包必须是public。
public class AtomicTest {
public static void main(String[] args) {
AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");
Dog dog = new Dog();
updater.compareAndSet(dog, dog.name, "white dog");
System.out.println(dog.name);
}
}
class Dog {
volatile String name = "black dog";
}
二、AtomicMarkableReference与AtomicStampedReference区别
AtomicMarkableReference不关心变量是否被更改过几次,只是单纯想知道是否被更改过,通过boolean值来标识是否被更改,而AtomicStampedReference通过int值来标识变量被更改的次数,可以解决ABA问题。其它功能都相似。
AtomicMarkableReference内部维护了一个private volatile Pair<V> pair,用于存储引用对象和boolean标识。
private static class Pair<T> {
final T reference;
final boolean mark;
private Pair(T reference, boolean mark) {
this.reference = reference;
this.mark = mark;
}
static <T> Pair<T> of(T reference, boolean mark) {
return new Pair<T>(reference, mark);
}
}
// 主要方法如下
public V get(boolean[] markHolder) {
Pair<V> pair = this.pair;
markHolder[0] = pair.mark;
return pair.reference;
}
public boolean compareAndSet(V expectedReference, V newReference,
boolean expectedMark,boolean newMark) {
Pair<V> current = pair;
// 如果预期值和新值一致返回true,否则更新为新的Pair
return
expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
public boolean attemptMark(V expectedReference, boolean newMark) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
(newMark == current.mark ||
casPair(current, Pair.of(expectedReference, newMark)));
}
字段原子类,以AtomicIntegerFieldUpdater为例,AtomicLongFieldUpdater类似AtomicIntegerFieldUpdater,源码比较简单略过
AtomicIntegerFieldUpdater特征
①字段必须是volatile类型的,在线程之间共享变量时保证立即可见
②调用者拥有操作对象字段的权限,那么就可以反射进行原子操作
③对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段
④只能是实例变量,不能是类变量,也就是说不能加static关键字
⑤只能是可修改变量,不能使final变量,因为final的语义就是不可修改
⑥对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater
// 只验证上述第3点
public class Parent {
public volatile int age = 50;
}
public class Son extends Parent{
// public volatile int age = 30;
}
public class AtomicTest {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.age); // 此处可以访问父类age,打印50
AtomicIntegerFieldUpdater aifU = AtomicIntegerFieldUpdater.newUpdater(Son.class,"age");
aifU.set(son, 34);
System.out.println(son.age);
}
}
// 运行结果如下:
Exception in thread "main" java.lang.RuntimeException: java.lang.NoSuchFieldException: age