java.util.concurrent.atomic(在JUC下的atomic包中)(jdk1.5开始提供)
这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
Atomic包下的这些类都是采用乐观锁策略CAS(又称为无锁操作)来更新数据的:假设所有线程访问共享资源的时候不会出现冲突,因此不会阻塞其他线程的操作。那么,如果出现冲突了怎么办:出现冲突就重试当前操作直到没有冲突为止。
对CAS最直观的理解:线程安全地更新变量,一般用synchronized,但是太重型、效率低,所以用CAS,它不通过synchronized同步的方式,而是使用一种乐观的策略,即先假设不会冲突直接更新,检测到冲突了再循环解决,直到没有冲突。
CAS的具体操作过程,以AtomicInteger类为例,是通过 Unsafe类下的native函数compareAndSwapInt(对应while()里的方法)自旋来保证原子性:???? 见本文“一、原子更新的基本类型”处代码解释
后面会讲一个 自旋锁:尝试获取锁的线程如果失败,不会阻塞,而是采用循环的方式去尝试获取锁,好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
CAS的问题
1. 自旋时间过长。由compareAndSwapInt函数可知,自旋时间过长会对性能是很大的消耗。
2. ABA问题。因为CAS会检查旧值有没有变化,这里存在这样一个有意思的问题。比如一个旧值A变为了成B,然后再变成A,刚好在做CAS时检查发现旧值并没有变化依然为A,但是实际上的确发生了变化。解决方案可以添加一个版本号可以解决。原来的变化路径A->B->A就变成了1A->2B->3A,或使用AtomicStampedReference工具类。
AtomicInteger atomicInteger = new AtomicInteger(1);//默认为0
atomicInteger.compareAndSet(1,2);
AtomicStampedReference<Integer> integerAtomicStampedReference = new AtomicStampedReference<Integer>(1,1);
integerAtomicStampedReference.compareAndSet(1,2,1,2);
因为变量的类型有很多种,所以在Atomic包里也提供了很多类,大致可以属于4种类型的原子更新方式,它们基本都是使用Unsafe实现的包装类,具体类包括:
一、原子更新的基本类型
AtomicBoolean、AtomicInteger、AtomicLong (jdk1.8在atomic包中推出了LongAdder类,为了解决自旋导致的性能问题)(以原子操作的方式更新各种数据类型)
//AtomicInteger示例:
AtomicInteger atomicInteger = new AtomicInteger(); //参数什么都不写的话,默认是0
System.out.println(atomicInteger.getAndIncrement());
//那么getAndIncrement是如何实现原子操作的呢?让我们一起分析其实现原理,getAndIncrement的源码如下:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1); //getAndIncrement()中调用了unsafe类已经封装好了的getAndAddInt(this, valueOffset, 1)方法,所以这里不重要(但是要注意这个方法unsafe.getAndAddInt(this, valueOffset, 1);不是native方法)
}
//该方法首先通过this.getIntVolatile(var1, var2); 获取var1指向的内存地址里var2的值,这里也就是获取旧的值;然后通过CAS的方式更新值为var5+var4,更新失败进入自循。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2); //先获取var1指向的内存地址里var2的值,这里也就是获取旧的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //再根据获取的旧值(参数3:var5),通过CAS的方式更新为新值(参数4:var5+var4),更新失败进入自循
/**
compareAndSwapInt:
* 如果当前数值是expected,则原子的将Java变量更新成x
* @return 如果更新成功则返回true
这个方法是Unsafe类的方法,是native方法
*/
//public final native boolean compareAndSwapInt(Object o , long offset , int expected, int x );
return var5;
}
getAndIncrement()是AtomicInteger的final方法,不可被重写;
getAndIncrement()内部调用了Unsafe类的getAndAddInt()方法,也是final的,也不可被重写;
getAndAddInt()内部的do中调用getIntVolatile,while中调用compareAndSwapInt,它俩都是Unsafe类中的native方法
LongAdder采用的方法是,共享热点数据分离的计数:将一个数字的值拆分为一个数组。不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多;要得到这个数字的话,就要把这个值加起来。相比AtomicLong,并发量大大提高。
优点:有很高性能的并发写的能力
缺点:读取的性能不是很高效,而且如果读取的时候出现并发写的话,结果可能不是正确的
二、原子更新的数组类型
AtomicIntegerArray:原子更新整型数组中的元素;
AtomicLongArray:原子更新长整型数组中的元素;
AtomicReferenceArray:原子更新引用类型数组中的元素
//以AtomicIntegerArray为例
static int[] value = new int[] { 1, 2 };
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0,3); //0是下标,3是更新值
System.out.println(ai.get(0));
System.out.println(value[0]);
}
输出接结果:
3
1
需要注意的是,数组value通过构造方法传递进去,然后AtomicIntergerArray会将当前数组复制一份,所以当AtomicIntergerArray对内部的数组元素进行修改时,不会影响传入的数组。
另外,关于Java数组初始化问题,再讲下:
1、数组必须指定大小,下面这种直接编译不通过
int[] a = new int[];//编译不通过
正答:
int[] a = new int[3];
or
int[] a = new int[]{1,2,3};
但
int[] a = new int[3]{1,2,3};//编译不通过
or
int[] a = {1,2,3}//长度也就固定为3了
or
int[] a = {}//长度固定为0
三、原子更新的引用类型
如果需要原子更新引用类型变量的话,为了保证线程安全,Atomic也提供了相关的类:
1. AtomicReference:原子更新引用类型;
2. AtomicReferenceFieldUpdater:原子更新引用类型里的字段;
3. AtomicMarkableReference:原子更新带有标记位的引用类型;
//以上几个类提供的方法几乎一样,所以此处我们仅以AtomicReference为例进行讲解,AtomicReference的使用示例代码如下:
public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
public static void main(String[] args) {
User user = new User("kenan", 15);
atomicUserRef.set(user);
User updateUser = new User("qq", 17);
//这个方法和atomicInteger.getAndIncrement()方法是同级别的
atomicUserRef.compareAndSet(user, updateUser); //点进去,发现该方法实际依靠了unsafe.compareAndSwapObject方法
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;
}
}
运行结果:
qq
17
其实现原理是依靠了unsafe.compareAndSwapObject方法:
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
四、原子更新字段类型
如果需要更新对象的某个字段,Atomic同样也提供了相应的原子操作类:
1. AtomicIntegeFieldUpdater:原子更新整型字段类;
2. AtomicLongFieldUpdater:原子更新长整型字段类;
3.AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。()所以有四个参数:期望的旧值,新值,期望的旧版本,新版本)
即atomicStampedReference.compareAndSet(四个参数)