1,前言
Java从JDK5开始提供了java.util.concurrent.atomic
包,包中的原子操作类提供一种用法简单、性能高效、线程安全地更新一个变量的方式。
因为变量的类型有很多种,所以在Atomic
包一共提供了13个类,属于4种类型的原子更新方式,分别是:
- 原子更新基本类型
- 原子更新数组
- 原子更新引用
- 原子更新属性(字段)
Atomic
包里的类基本都是使用Unsafe
实现的包装类
值得一提的是,以AtomicInteger
其内部用于存储目标整数值的变量是用volatile
进行修饰的
public class AtomicInteger extends Number implements java.io.Serializable {
......
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
......
}
2,原子操作类
2.1,原子更新基本类型
AtomicBoolean
:原子更新布尔类型AtomicInteger
:原子更新整型AtomicLong
:原子更新长整型
三个类提供的方法几乎一模一样,所以以AtomicInteger
为例
方法 | 描述 |
---|---|
public final int addAndGet(int delta) | 以原子方式将输入的数值和实例中的值相加,并返回结果 |
public final boolean compareAndSet(int expect, int update) | 如果输入的数值等于预期值,则以原子方式将该值设置为输入的值 |
public final int getAndIncrement() | 以原子方式将当前值加1,注意,这里返回的是自增前的值 |
public final void lazySet(int newValue) | 最终会设置为newValue ,使用lazySet设置值后,可能会导致其他线程在一小段时间内还是可以读到旧的值 |
public final int getAndSet(int newValue) | 用原子的方式设置位newValue ,并返回旧值 |
Atomic
包提供了3种基本类型的原子更新,以AtomicBoolean
源码为例:
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
可以发现它是先把Boolean
转为整型,在使用compareAndSwapInt
进行CAS,所以原子更新char,float,double
变量也可以用类似的思路来实现。
2.2,原子更新数组
通过原子的方式更新数组里的某个元素,Atomic
包提供了以下4个类
AtomicIntegerArray
:原子更新整型数组里的元素AtomicLongArray
:原子更新长整型数组里的元素AtomicReferenceArray
:原子更新引用类型数组里的元素
public static void main(String[] args) {
static int[] value = new int[]{1,2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
//getAndSet(下标,新元素)方法是用原子方式将新元素放置于指定下标位置,并返回旧值
ai.getAndSet(0, 3);
System.out.println(ai.get(0)); //输出 3
System.out.println(value[0]); //输出 1
}
这里值得注意的是,数组value
通过构造方法传递进去,然后AtomicIntegerArray
会将当前数组复制一份,然后当AtomicIntegerArray
对内部的数组元素进行修改时,不会影响传入的数组。
public AtomicIntegerArray(int[] array) {
// Visibility guaranteed by final field guarantees
this.array = array.clone();
}
2.3,原子更新引用类型
原子更新基本类型的AtomicInteger
只能更新一个变量,如果是要原子更新多个变量,就需要使用这个原子更新引用类型提供的类
AtomicReference
:原子更新引用类型AtomicReferenceFieldUpdater
:原子更新引用类型里的字段AtomicMarkableReference
:原子更新带有标志位的引用类型
以AtomicReference
为例
public class AtomicReferenceTest {
public static AtomicReference<user> atomicUserRef = new
AtomicReference<user>();
public static void main(String[] args) {
User user = new User("conan", 15);
atomicUserRef.set(user);
User updateUser = new User("Shinichi", 17);
//用CAS的方式更新引用
atomicUserRef.compareAndSet(user, updateUser);
System.out.println(atomicUserRef.get().getName());
System.out.println(atomicUserRef.get().getOld());
}
static class User {
private String name;
private int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}
2.4,原子更新字段类
如果需要原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic
包提供了以下3个类进行原子字段更新
AtomicIntegerFieldUpdater
:原子更新整型的字段的更新器AtomicLongFieldUpdater
:原子更新长整型字段的更新器AtomicStampedReference
:原子更新带有版本号的引用类型。该类将整数值与引 用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用 CAS 进行 原子更新时可能出现的 ABA 问题
以AtomicIntegerFieldUpdater
为例:
public class AtomicIntegerFieldUpdaterTest {
// 创建原子更新器,并设置需要更新的对象类和对象的属性
//由于AtomicIntegerFieldUpdater是一个抽象类,所以使用的时候需要使用静态方法:newUpadter()创建一个更新器,并且设置想要更新的类和属性;并且更新类的字段(属性)必须使用 public volatile 修饰符
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "old");
public static void main(String[] args) { // 设置柯南的年龄是 10 岁
User conan = new User("conan", 10);
// 柯南长了一岁,但是仍然会输出旧的年龄 ===》输出:10
System.out.println(a.getAndIncrement(conan));
// 输出柯南现在的年龄 ===》输出:11
System.out.println(a.get(conan));
}
public static class User {
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}