原子类操作
JUC中的原子性操作类,都是使用非阻塞算法CAS实现的,相比使用锁实现原子操作在性能上有了较大的提升。
Atomic原子类的底层源码:
//调用unsafe方法,原子性设置value值为原始值 +1,返回递增后的值
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
//getAndAddLong是Unsafe类中的方法,该方法是原子性操作的
/* 参数列表说明
1)Object var1:AtomicLong的实例
2)long var2:变量在AtomicLong中的偏移量
3)long var4:要设置的第二个变量的值
*/
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
//getLongVolatile是一个native修饰的方法
//compareAndSwapLong也是一个本地方法
AtomicLong的不足???
- AtomicLong的CAS操作提供了非阻塞的原子性操作,性能比使用同步器更好。但是在高并发下大量线程会同时去竞争同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS的操作,从而浪费CPU资源。
- 可以使用原子性递增或递减类LongAdder来克服AtomicLong在高并发下使用的缺点。可以使用LongAdder类解决高并发情况下多个线程竞争同一资源导致其他未占有资源的变量自旋等待,消耗CPU资源。
1. 原子更新基本数据类型
使用原子的方式更新基本类型,Atomic包提供了以下3个类:
- AtomicBoolean:原子更新布尔类型。
- AtomicInteger:原子更新整型。
- AtomicLong:原子更新长整型。
以AtomicInteger为例:
【注】Integer使用了对象缓存机制,默认范围是 -128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是使用new,因为valueof使用缓存,而new一定会创建新的对象分配新的内存空间。
- int addAndGet(int delta):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
- boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
- int getAndIncrement():以原子方式将当前值加1,这里返回的是自增前的值。等价于 i++,先加后用
- int getAndDecrement():以原子方式将当前值减1。
- void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
- int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
public class AtomicIntegerTest {
public static void main(String[] args) {
AtomicInteger ac = new AtomicInteger();
ac.set(200); //设置一个值
ac.addAndGet(2);
System.out.println(ac.get()); //202
System.out.println(ac.compareAndSet(202, 300)); //true
System.out.println(ac.get()); //获取结果 300
System.out.println("自增1 "+ac.getAndIncrement()); //300
System.out.println(ac.get()); //301
System.out.println(ac.getAndSet(400)); //301,返回原值
System.out.println(ac.get()); //400
}
}
Unsafe只提供了3种CAS方法, 如下所示:
查看AtomicBoolean源码,发现它是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新char、float和double变量也可以用类似的思路来实现。
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
2. 原子更新数组
通过原子的方式更新数组里的某个元素,Atomic包提供了以下4个类:
- AtomicIntegerArray:原子更新整型数组里的元素。
- AtomicLongArray:原子更新长整型数组里的元素。
- AtomicReferenceArray:原子更新引用类型数组里的元素
AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下:
- int addAndGet(int i,int delta):以原子方式将输入值与数组中索引
i
的元素相加。 - boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置
i
的元素设置成update值。
// 构造方法的源码
private final int[] array;
public AtomicIntegerArray(int length) {
array = new int[length]; // 传入一个整数,初始化一个length长的零值数组
}
public AtomicIntegerArray(int[] array) {
this.array = array.clone(); //将array克隆一份给AtomicIntegerArray的array属性,当前数组的更改不会影响原数组。深拷贝
}
需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响传入的数组。
// 测试
public class AtomicIntegerArrayTest {
private static int[] values = {1, 2, 3, 4};
private static AtomicIntegerArray arr = new AtomicIntegerArray(values);
public static void main(String[] args) {
arr.addAndGet(0,10);
System.out.println(arr.compareAndSet(3, 4, 20)); // true
for (int i = 0; i < arr.length(); i++) {
System.out.print(arr.get(i) + " "); // 11 2 3 20
}
System.out.println();
for (int i = 0; i < values.length; i++) {
System.out.print(values[i] + " "); // 1 2 3 4 对AtomicIntegerArray数组的更改不会影响原来数组的值。
}
}
}
3. 原子更新引用类型
原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下3个类:
- AtomicReference:原子更新引用类型。只能存储一个对象实例
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef,booleaninitialMark)
以AtomicReference为例:
-
构造器源码
public AtomicReference(V initialValue) { value = initialValue; } //Creates a new AtomicReference with null initial value. public AtomicReference() { }
-
set()方法源码
private volatile V value; //表示一个AtomicReference实例只能存放一个value public final void set(V newValue) { value = newValue; }
-
compareAndSet()方法,比较并交换
public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
-
测试
public class AtomicReferenceTest { private static AtomicReference<User> ar = new AtomicReference<>(); public static void main(String[] args) { User u1 = new User("张三", 20); User u2 = new User("李四", 21); ar.set(u1); // ar.set(u2); System.out.println(ar.get()); //User{name='张三', age=20} ar.compareAndSet(u1, u2); System.out.println(ar.get()); //User{name='李四', age=21} } } class User{ private String name; private Integer age; ...... }
4. 原子更新字段类
如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段更新:
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
要想原子地更新字段类需要两步:
- 第一步,因为原子更新字段类都是抽象类**,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。**
- 第二步,更新类的字段(属性)必须使用public volatile修饰符。
使用AtomicIntegerFieldUpdater作为测试:
public class AtomicIntegerFieldUpdaterTest {
//参数1:引用 的类型
//参数2:需要修改的字段名称 fieldName
// 第一步,使用静态方法 newUpdater()创建一个更新器
private static AtomicIntegerFieldUpdater<User> au = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
public static void main(String[] args) {
User user = new User("铅笔", 18);
System.out.println(au.getAndIncrement(user)); //18
System.out.println(au.get(user)); //19
}
}
//第一步的源码
@CallerSensitive
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
String fieldName) {
return new AtomicIntegerFieldUpdaterImpl<U>
(tclass, fieldName, Reflection.getCallerClass());
}
//Reflection.getCallerClass():通过反射获取调用的类
public class User{
private String name;
// 第二步,属性需要添加public volatile修饰
public volatile int age;
.......
}
测试时出现的异常
1、在需要更新的类的属性中未使用正确的类型。不能使用包装类型Integer,需要使用基本类型int
java.lang.ExceptionInInitializerError
Caused by: java.lang.IllegalArgumentException: Must be integer type
2、在需要更新的类的属性中不添加 volatile
关键字,则会出现以下异常:
java.lang.ExceptionInInitializerError
Caused by: java.lang.IllegalArgumentException: Must be volatile type
3、需要更改的属性不能使用 private
访问控制符修饰
java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: java.lang.IllegalAccessException: Class com.wei.atomicDemo.AtomicIntegerFieldUpdaterTest can not access a member of class com.wei.atomicDemo.User with modifiers "private volatile"