并发编程原子类完整教程:Java并发编程之原子操作类实战教程
AtomicIntegerFieldUpdater 是一个基于反射的工具类(原子操作),它能对指定类的 指定的volatile引用字段 进行 原子更新,需要注意的是这个字段不能是private的。简单理解就是对某个类中,被volatile修饰的字段进行原子更新。
- AtomicIntegerFieldUpdater 是 Doug Lea 在Java 5中写的atomic classes 中Filed Updater的一部分,本质上是volatile字段的包装器。
- ⼀个类中的volatile成员属性获取值、设定为某个值的这两个操作是⾮原⼦的,若想将其变为原⼦操作,可以通过 AtomicIntegerFieldUpdater 来实现。
- AtomicReferenceFieldUpdater 是针对引用类型属性,而 AtomicIntegerFieldUpdater 是针对整形的属性。
- AtomicIntegerFieldUpdater 在高并发场景下,自旋 CAS 长时间失败会导致 CPU 飙升。
1、AtomicIntegerFieldUpdater 的使用条件
1.1 应用场景
如果一个类是自己编写的,可以在编写的时候把成员变量定义为Atomic类型。但如果是一个已经有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原子操作,就需要AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater。
1.2 使用条件
原子更新器 AtomicIntegerFieldUpdater 的使用存在比较苛刻的条件:
-
只能是实例变量,不能是类变量,也就是说不能加static关键字。
-
只能是可修改变量,不能是final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
-
字段必须是volatile修饰的,也就是数据本身是读一致的,即在线程之间共享变量时保证立即可见。eg: volatile int value = 3。
-
字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
2、AtomicIntegerFieldUpdater 主要方法
2.1 实例的创建
AtomicIntegerFieldUpdater 有一个 protected 的无参数构造方法,只能供子类使用。所以一般情况下创建一个 AtomicIntegerFieldUpdater 实例需要使用该类提供的一个静态方法 newUpdater。
AtomicIntegerFieldUpdater 可以为一个用于更新指定类的声明为volatile类型的属性进行原子性更新,通过调用 AtomicIntegerFieldUpdater 的静态方法 newUpdater
创建实例,接受三个参数:
- 第一个参数:包含要更新属性/字段的类的类型,即需要更新字段所在的class类
- 第二个参数:更新属性/字段的名称
public static void main(String[] args) {
Student student = new Student();
//创建AtomicIntegerFieldUpdater对象
AtomicIntegerFieldUpdater<Student> studentAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age");
//打印age并将age+1
System.out.println(studentAtomicIntegerFieldUpdater.getAndIncrement(student));
System.out.println(student.age);
}
//测试类
public class Student {
//因为是用反射实现的这里必须要使用public修饰
public volatile int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
该示例代码通过调用 AtomicIntegerFieldUpdater.newUpdater(Student.class, "age")
静态方法生成 Student
类的 Integer
类型的 age
字段的原子修改器 updater,然后调用它的 getAndIncrement
方法判断 student 对象的 age 值增加 1 并返回旧值。
2.2 主要方法
//如果当前值==预期值,那么就原子的将当前Updater管理的给定的对象的字段设置为给定的更新值
/这个方法只对compareAndSet和set提供原子保证,但是对于字段的其他修改不一定能够提供保证
//obj 是要进行设置的目标对象
//expect 是期待的目标对象
//update 要更新设置的值
//如果返回true说明设置成功了
//如果obj不是构造方法里给出的类型的实例,这个方法可能会抛出ClassCastException
//注: 按照package中对于false的定义,在期待值和当前值不同时,返回false
public abstract boolean compareAndSet(T obj, int expect, int update);
//方法的基本描述跟上面的一样,但是略有不同的地方是
//这个方法可能会由于错误而失败,并且不提供顺序保证,
//因此很少用做compareAndSet的替换方法
//参数以及返回和异常与上一个方法一样
public abstract boolean weakCompareAndSet(T obj, int expect, int update);
//将这个Updater所管理的给定对象的字段设置为给定的新元素
//这个操作相对于compareAndSet提供了操作的保证
public abstract void set(T obj, int newValue);
//可以保证最终会把目标中的被Updater管理的元素更新为给定元素
public abstract void lazySet(T obj, int newValue);
//读取被当前Updater管理的字段的当前值
public abstract int get(T obj);
//原子的被当前Updater管理的目标对象中的字段更新为指定值
//同时返回原来的元素值
public int getAndSet(T obj, int newValue)
//用于将当前Updater管理的目标对象中的字段更新为+1后的值
//返回原来的值
public int getAndIncrement(T obj)
//跟上一个方法相似,区别在于这个方法适用于-1
public int getAndDecrement(T obj)
//用于将当前Updater管理的目标对象中的字段更新为+delta后的值
//返回原来的值
public int getAndAdd(T obj, int delta)
//用于将当前Updater管理的目标对象中的字段更新为+1后的值
//返回新值
public int incrementAndGet(T obj)
//跟上一个方法相似,区别在于这个方法-1
public int decrementAndGet(T obj)
//用于将当前Updater管理的目标对象中的字段更新为+delta后的值
//返回新值
public int addAndGet(T obj, int delta)
下面是从 JDK1.8 开始提供的一些方法:
- 这些方法主要是利用 IntUnaryOperator 和 IntBinaryOperator 增加的对方法执行结果的CAS原子操作
- 注意:这些方法要求这些方法必须是无副作用的,因为在线程争用失败的情况下,需要再次调用
//返回旧元素
public final int getAndUpdate(T obj, IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get(obj);
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(obj, prev, next));
return prev;
}
//返回新元素
public final int updateAndGet(T obj, IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get(obj);
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(obj, prev, next));
return next;
}
//执行方法的时候传递的第一个参数是旧元素,第二个参数是x
//返回旧元素
public final int getAndAccumulate(T obj, int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get(obj);
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(obj, prev, next));
return prev;
}
//跟上一个方法相似
//返回新元素
public final int accumulateAndGet(T obj, int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get(obj);
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(obj, prev, next));
return next;
}
3、AtomicIntegerFieldUpdater 演示示例
3.1 演示示例1
public class ConcurrencyTest {
public static AtomicIntegerFieldUpdater<ConcurrencyTest> updater = AtomicIntegerFieldUpdater.newUpdater(ConcurrencyTest.class,"count");
private volatile int count = 100;
public int getCount(){
return count;
}
private static ConcurrencyTest concurrencyTest = new ConcurrencyTest();
public static void main(String[] args) throws InterruptedException {
if(updater.compareAndSet(concurrencyTest,100,120)){
System.out.println("update success "+concurrencyTest.getCount());
}
if(updater.compareAndSet(concurrencyTest,100,130)){
System.out.println("update success "+concurrencyTest.getCount());
} else {
System.out.println("update fail "+concurrencyTest.getCount());
}
}
}
在上述代码示例中,创建 AtomicIntegerFieldUpdater 对象的时候需要指定对象的类型和要更新类中的哪个字段,此处要更新的是 ConcurrencyTest 类中的 count 字段。
count 初始值为100,所以第一次CAS操作时满足要求,将 count 的值更新为120;第二次比较时当前对象中的字段值已经为120,和100比较时不相等,所以比较失败,进入else中,所以最终输出如下:
update success 120
update fail 120
3.2 演示示例2
- 计票类:
public class Detail {
public volatile int numberTimesInvoked;
public int getNumberTimesInvoked() {
return numberTimesInvoked;
}
public void setNumberTimesInvoked(int numberTimesInvoked) {
this.numberTimesInvoked = numberTimesInvoked;
}
}
- 辅助代码为了验证并发的情况:
public class AtomicNumber {
public static int A=0;
}
- 编写测试计算类,模拟多线程环境操作 AtomicIntegerFieldUpdater:
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterCounter {
//构造计数对象
private AtomicIntegerFieldUpdater<Detail> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Detail.class,"numberTimesInvoked");
public Integer addOne(Detail detail){
return this.atomicIntegerFieldUpdater.incrementAndGet(detail);
}
public Integer subOne(Detail detail){
return this.atomicIntegerFieldUpdater.decrementAndGet(detail);
}
public static void main(String[] args) throws InterruptedException {
final Detail detail = new Detail();
final AtomicIntegerFieldUpdaterCounter a = new AtomicIntegerFieldUpdaterCounter();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
AtomicNumber.A++;
a.addOne(detail);
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100000; i++) {
AtomicNumber.A++;
a.subOne(detail);
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(AtomicNumber.A);
System.out.println(detail.getNumberTimesInvoked());
}
}
代码运行结果:
197169
0
从结果可以看出:利用AtomicIntegerFieldUpdater 可以保证对象属性不会发生并发访问的情况。
4、源码分析
AtomicIntegerFieldUpdater 的代码很简单,下面对 AtomicIntegerFieldUpdater 部分源码进行解析。
4.1 实现基础 - 构造方法
AtomicIntegerFieldUpdater 有一个 protected 的构造方法,只能供子类使用。所以一般情况下创建一个 AtomicIntegerFieldUpdater 实例需要使用该类提供的一个静态方法 newUpdater。
// 受保护的无操作构造方法,供子类使用。一般情况下都使用newUpdater创建对象。
protected AtomicIntegerFieldUpdater() {
}
4.2 实现基础 - 创建实例的静态方法
创建实例的静态方法为 newUpdater:
/**
* 基于指定的 Class 类型和字段名称创建一个 AtomicIntegerFieldUpdater 实例,
* 字段类型必须是 volatile int。
*/
@CallerSensitive
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
String fieldName) {
return new AtomicIntegerFieldUpdaterImpl<U>
(tclass, fieldName, Reflection.getCallerClass());
}
参数:
- tclass - 要更新字段的对象类。
- fieldName - 要更新的字段名称。
返回:
- 更新程序
AtomicIntegerFieldUpdaterImpl实现类源码:
AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final int modifiers;
try {
//将这个方法作标示为特权方法
field = AccessController.doPrivileged(
new PrivilegedExceptionAction<Field>() {
public Field run() throws NoSuchFieldException {
return tclass.getDeclaredField(fieldName);
}
});
// 获取字段修饰符(此处返回的是int类型)
// PUBLIC: 1
// PRIVATE: 2
// PROTECTED: 4
// STATIC: 8
// FINAL: 16
// SYNCHRONIZED: 32
// VOLATILE: 64
// TRANSIENT: 128
// NATIVE: 256
// INTERFACE: 512
// ABSTRACT: 1024
// STRICT: 2048
//返回值是各项修饰符的加和。自己做判断可能有点麻烦
// 可以通过Modifier.toString(int mod)方法来进行解析
//或者通过Modifier里面的isXXX相关方法判断
//Modifier.toString方法放到Modifier里面说
modifiers = field.getModifiers();
//判断调用类对对象类在 modifiers修饰符下有权限调用
//这个方法主要用于校验给定的调用者(第一个参数)
//第二个参数是目标的类类型
//第三个参数在静态的情况系可以是null
//第四个参数是访问修饰
//判断是否可以访问目标对象中构造方法、字段、普通方法等
//如果没有访问权限会抛出IllegalAccessException
sun.reflect.misc.ReflectUtil.ensureMemberAccess(caller, tclass, null, modifiers);
//目标类的类加载器
ClassLoader cl = tclass.getClassLoader();
//调用者的类加载器
ClassLoader ccl = caller.getClassLoader();
//这个判断条件要求满足目标类和调用者不能使同一个类加载器
//调用者不能是根加载器
//目标类加载器是根加载器或者调用者类加载器和目标类加载器的关系不满足isAncestor(没有委托链关系)
if ((ccl != null) && (ccl != cl) &&
((cl == null) || !isAncestor(cl, ccl))) {
//如果调用方不是被调用方的祖先的话,检查包访问权限
//校验目标类的包的可访问性
sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
}
} catch (PrivilegedActionException pae) {
throw new RuntimeException(pae.getException());
} catch (Exception ex) {
throw new RuntimeException(ex);
}
Class<?> fieldt = field.getType();
//判断是否是int
if (fieldt != int.class)
throw new IllegalArgumentException("Must be integer type");
//判断是否为volatile修饰
if (!Modifier.isVolatile(modifiers))
throw new IllegalArgumentException("Must be volatile type");
//这步是为了之后的权限校验,如果不为null 之后会调用校验包权限
//访问protected字段需要是同包下或者有父子关系的情况
//这个判断条件指出,如果是
//在字段为Protected,目标类是调用类的父类,调用类和目标类不是同一个包下时
//cclass存储调用者的类
//上面条件任何一个不满足都指向tclass
//包括不是保护域,没有父子关系,在同一个包下面
this.cclass = (Modifier.isProtected(modifiers) &&
caller != tclass) ? caller : null;
//记录目标class
this.tclass = tclass;
//获取偏移量
offset = unsafe.objectFieldOffset(field);
}
4.3 实现基础 - 主要方法
4.3.1 set方法
set 方法设置由此更新程序所管理的给定对象的字段。对于 compareAndSet 的后续调用,此操作可以确保充当可变存储。
public final void set(T obj, int newValue) {
accessCheck(obj);
U.putIntVolatile(obj, offset, newValue);
}
参数:
- obj - 要设置其字段的对象
- newValue - 新值
4.3.2 get方法
get 方法获取由给定对象在字段中保持的当前值。
public final int get(T obj) {
accessCheck(obj);
return U.getIntVolatile(obj, offset);
}
参数:
- obj - 要获取其字段的对象
返回:
- 当前值
4.3.3 lazySet方法
lazySet 方法最终将此更新程序管理的给定对象的字段设置为给定的更新值。
public final void lazySet(T obj, int newValue) {
accessCheck(obj);
U.putOrderedInt(obj, offset, newValue);
}
lazySet与set实现的功能类似,二者区别如下:
- lazySet实现的是最终一致性,set实现的是强一致性
- lazySet:多线程并发时,线程A调用unsafe.putOrderedInt更新newValue值时,只是把线程A内存中的元素更新成功了,但其它线程此时看不到线程A更新的newValue值,需过一会,线程A更新的newValue才会刷入到主内存中,此时其它线程才能看到线程A更新的值
- set: 多线程并发时,线程A更新的值会立即刷入主内存中
- 总结:对于数据一致性要求高的建议用set
4.3.4 获取设置方法
getAndSet 方法为获取设置方法,即以原子方式将此更新程序管理的给定对象的字段设置为给定值并返回旧值。
public final int getAndSet(T obj, int newValue) {
accessCheck(obj);
return U.getAndSetInt(obj, offset, newValue);
}
参数:
- obj - 要获取并设置其字段的对象
- newValue - 新值
返回:
- 以前的值
4.3.5 比较设置方法
比较设置方法有 compareAndSet 和 weakCompareAndSet。
- compareAndSet()方法: 如果当前值 == 预期值,则以原子方式将此更新程序所管理的给定对象的字段值设置为给定的更新值。
- weakCompareAndSet()方法: 如果当前值 == 预期值,则以原子方式将此更新程序所管理的给定对象的字段值设置为给定的更新值。
//CAS操作
public final boolean compareAndSet(T obj, int expect, int update) {
accessCheck(obj);
return U.compareAndSwapInt(obj, offset, expect, update);
}
//弱CAS操作
public final boolean weakCompareAndSet(T obj, int expect, int update) {
accessCheck(obj);
return U.compareAndSwapInt(obj, offset, expect, update);
}
参数:
- obj - 有条件地设置其字段的对象
- expect - 预期值
- update - 新值
返回:
- 如果成功,则返回 true。
4.3.6 原子增加属性的值,并返回旧值
以原子方式将此更新器管理的给定对象的当前值 + 1/N,并返回旧值。
//增加delta后返回原来的元素
public final int getAndAdd(T obj, int delta) {
accessCheck(obj);
return U.getAndAddInt(obj, offset, delta);
}
//+1然后返回原来的元素,实际调用的是getAndAdd
public final int getAndIncrement(T obj) {
return getAndAdd(obj, 1);
}
/**
* 检查目标对象是否是指定 Class 类型的实例
*/
private void accessCheck(T obj) {
if (!cclass.isInstance(obj)) {
throwAccessCheckException(obj);
}
}
4.3.7 原子减少属性的值,并返回旧值
以原子方式将此更新器管理的给定对象的当前值 - 1,并返回旧值。
//-1然后返回原来的元素,实际调用的是getAndAdd
public final int getAndDecrement(T obj) {
return getAndAdd(obj, -1);
}
4.3.8 原子更新属性的值,并返回旧值
以原子方式将此更新器管理的给定对象的当前值更新为一元函数式接口的计算值,并返回旧值
/**
* 以原子方式将此更新器管理的给定对象的当前值更新为一元函数式接口的计算值,并返回旧值
*/
public final int getAndUpdate(T obj, IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get(obj);
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(obj, prev, next));
return prev;
}
以原子方式将此更新器管理的给定对象的当前值更新为二元函数式接口的计算值,并返回旧值
/**
* 以原子方式将此更新器管理的给定对象的当前值更新为二元函数式接口的计算值,并返回旧值
*/
public final int getAndAccumulate(T obj, int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get(obj);
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(obj, prev, next));
return prev;
}
4.3.9 原子增加属性的值,并返回新值
以原子方式将此更新器管理的给定对象的当前值 + 1/N,并返回新值。
//+1然后返回新元素,实际调用的是getAndAdd
public final int incrementAndGet(T obj) {
return getAndAdd(obj, 1) + 1;
}
//加delta然后返回新元素,实际调用的是getAndAdd
public final int addAndGet(T obj, int delta) {
return getAndAdd(obj, delta) + delta;
}
4.3.10 原子减少属性的值,并返回新值
以原子方式将此更新器管理的给定对象的当前值 - 1,并返回新值。
//-1然后返回新元素,实际调用的是getAndAdd
public final int decrementAndGet(T obj) {
return getAndAdd(obj, -1) - 1;
}
4.3.11 原子更新属性的值,并返回新值
以原子方式将此更新器管理的给定对象的当前值更新为一元函数式接口的计算值,并返回新值
/**
* 以原子方式将此更新器管理的给定对象的当前值更新为一元函数式接口的计算值,并返回新值
*/
public final int updateAndGet(T obj, IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get(obj);
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(obj, prev, next));
return next;
}
以原子方式将此更新器管理的给定对象的当前值更新为二元函数式接口的计算值,并返回新值
/**
* 以原子方式将此更新器管理的给定对象的当前值更新为二元函数式接口的计算值,并返回新值
*/
public final int accumulateAndGet(T obj, int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get(obj);
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(obj, prev, next));
return next;
}
评论区