《Java并发编程的艺术》读书笔记 第七章 Java中的13个原子操作类
1.原子更新基本类型类
使用原子的方式更新基本类型,Atomic包提供了3个类:
-
AtomicBoolean:原子更新布尔类型
-
AtomicInteger:原子更新整型
-
AtomicLong:原子更新长整型
以AtomicInteger为例介绍。AtomicInteger常用方法如下:
-
int addAndGet(int delta):以原子方式将输入的数值与实例中的数值(AtomicInteger里的value)相加,并返回结果。
-
boolean compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
-
int getAndIncrement():以原子方式将当前值加1,注意,这里返回的是自增之前的值。
-
void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其它线程在之后的一小段时间内还是可以读到旧的值。
-
int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
getAndIncrement()的源码如下所示():
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
for循环体的第一步先取得AtomicInteger里存储的数值,第二步对AtomicInteger的当前数值进行+1操作,关键的第三步调用compareAndSet(int expect,int update)方法来进行原子更新操作。
Atomic包提供了3种基本类型的原子更新,但是Java的基本类型里还有char、float和double等。那么其它基本类型如何更新?由于Atomic包里的类基本都是使用Unsafe实现的,那么看一下Unsafe的源码。
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);
Unsafe只提供了3种CAS方法,查看AtomicBoolean源码可以发现它是先把Boolean转换成整型,然后在使用compareAndSwapInt进行CAS操作,所以原子更新char、float和double变量也可以用类似的思路来实现。
2.原子更新数组
通过原子的方式更新数组的某个元素,有下面3个类:
-
AtomicIntegerArray:原子更新整型数组的元素
-
AtomicLongArray:原子更新长整型数组的元素
-
AtomicReferenceArray:原子更新引用类型数组里的元素
AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,常用方法如下:
//以原子方式将输入值和数组中索引为i的元素象加 int addAndGet(int i,int delta) //如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值 boolean compareAndSet(int i,int expect,int update)
以AtomicIntegerArray为例讲解,看如下代码:
public class Test01 { static int []val=new int[]{1,2}; static AtomicIntegerArray ai=new AtomicIntegerArray(val); public static void main(String[] args) { ai.getAndSet(0,3); System.out.println(ai.get(0)); System.out.println(val[0]); } } /* 3 1 */
数组val通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以虽然AtomicIntegerArray对内部的数组元素修改,但不会影响传入的数组。
3.原子更新引用类型
原子更新基本类型的AtomicInteger只能更新1个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下3个类:
- AtomicReference:原子更新引用类型
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和引用类型。构造方法是
AtomicMarkableReference(V initialRef,boolean initialMark)
。
4.原子更新字段类
如果需要原子的更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段更新。
-
AtomicIntegerFieldUpdater:源自更新整型的字段的更新器
-
AtomicLongFieldUpdater:原子更新长整型字段的更新器
-
AtomicStampedReference:原子更新带有版本号的引用类型,可解决之前提到的CAS操作带来的ABA问题。
要想原子的更新字段类需要两步:首先,因为原子更新字段类都是抽象类,每次使用的时候被絮使用静态方法new Updater()
创建一个更新器,并且需要设置想要更新的类和属性。第二部,更新类的字段必须使用public volatile
修饰符。
以AtomicIntegerFieldUpdater为例,下面展示下如何使用。
public class Test01 {
//创建原子更新其,并设置需要更新的对象类和对象的属性
private static AtomicIntegerFieldUpdater<Person> a=AtomicIntegerFieldUpdater.newUpdater(Person.class,"age");
public static void main(String[] args) {
Person jack=new Person("Jack",20);
System.out.println(a.getAndIncrement(jack));
System.out.println(a.get(jack));
}
}
public class Person {
private String name;
public volatile int age;//此处需要注意
public Person(String name,int age){
System.out.println("有参构造器");
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}