前言
emmmm,在写文章前我也翻阅了好多资料和书籍,其实大家在对原子类方法的使用介绍基本都没问题,但是对于java中原子类的个数是五花八门,下面我就把自己都认知和书籍资料结合起来给大家简单都介绍下java中原子类的应用。
参考文献
《Java并发编程的艺术》
正文
关于原子类个数说明
在JDK7包括7之前,java原子类有12个,图片如下,有些资料说有13个,多出来的是 AtomicBooleanArray 类,可是我在JDK8之前的源码里并没有发现有这个类,当然我也没去8以上的版本去看,所以这里不确定这个类到底在哪个版本中存在。
在JDK8时出现了4个原子操作类,分别是如下图片所示
原子更新基本类型类
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
- AtomicBoolean: 原子更新布尔类型。
- AtomicInteger: 原子更新整型。
- AtomicLong: 原子更新长整型。
以上3个类提供的方法几乎一模一样,以AtomicInteger为例进行详解,AtomicIngeter的常用方法如下:
- int addAndGet(int delta): 以原子的方式将输入的数值与实例中的值相加,并返回结果。
- boolean compareAndSet(int expect, int update): 如果输入的值等于预期值,则以原子方式将该值设置为输入的值。
- int getAndIncrement(): 以原子的方式将当前值加 1,注意,这里返回的是自增前的值,也就是旧值。
- void lazySet(int newValue): 最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
- int getAndSet(int newValue): 以原子的方式设置为newValue,并返回旧值。
代码示例
static AtomicInteger ai =new AtomicInteger(1);
public static void main(String[] args) {
System.out.println(ai.getAndIncrement());
System.out.println(ai.get());
}
输出结果
1
2
下面我们看看getAndIncrement() 是如何实现原子操作的
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
代码解析
我们取得了旧值,然后把要加的数传过去,调用getAndAddInt () 进行原子更新操作,实际最核心的方法是 compareAndSwapInt(),使用CAS进行更新。我们Unsafe只提供了3中CAS操作,
另外注意,AtomicBoolean 是把Boolean转成整型,在使用 compareAndSwapInt 进行操作的。
/**
* 如果当前数值是var4,则原子的将java变量更新成var5或var6
* @return 如果更新成功返回true
*/
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);
原子更新数组
通过原子的方式更新数组里的某个元素,Atomic包提供了以下的3个类:
有的资料说4个,笔者水平有限,真的没有发现第四个,如果哪位大佬知道的话,可以下方留言区指出来。
- AtomicIntegerArray: 原子更新整型数组里的元素。
- AtomicLongArray: 原子更新长整型数组里的元素。
- AtomicReferenceArray: 原子更新引用类型数组里的元素。
这三个类的最常用的方法是如下两个方法:
- get(int index):获取索引为index的元素值。
- compareAndSet(int i, int expect, int update): 如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置为update值。
下面以 AtomicReferenceArray 举例如下
static int[] value =new int[]{1,2};
static AtomicIntegerArray ai =new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0,2);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
输出结果
3
1
原子更新引用类型
原子更新基本类型的AtomicInteger,只能更新一个值,如果更新多个值,比如更新一个对象里的值,那么就要用原子更新引用类型提供的类,Atomic包提供了以下三个类:
- AtomicReference: 原子更新引用类型。
- AtomicReferenceFieldUpdater: 原子更新引用类型的字段。
- AtomicMarkableReferce: 原子更新带有标记位的引用类型,可以使用构造方法更新一个布尔类型的标记位和引用类型。
public static AtomicReference<User> ai = new AtomicReference<User>();
public static void main(String[] args) {
User u1 = new User("pangHu", 18);
ai.set(u1);
User u2 = new User("piKaQiu", 15);
ai.compareAndSet(u1, u2);
System.out.println(ai.get().getAge() + ai.get().getName());
}
static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
输出结果
piKaQiu 15
代码分析
我们吧对象放到 AtomicReference 中,然后调用 compareAndSet () 原子操作替换,原理和 AtomicInteger 相同,只是调用的是 compareAndSwapObject() 方法。
原子更新字段类
如果需要原子的更新类里某个字段时,需要用到原子更新字段类,Atomic包提供了3个类进行原子字段更新:
- AtomicIntegerFieldUpdater: 原子更新整型的字段的更新器。
- AtomicLongFieldUpdater: 原子更新长整型字段的更新器。
- AtomicStampedFieldUpdater: 原子更新带有版本号的引用类型。
//创建原子更新器,并设置需要更新的对象类和对象的属性
private static AtomicIntegerFieldUpdater<User> ai = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
public static void main(String[] args) {
User u1 = new User("pangHu", 18);
//原子更新年龄,+1
System.out.println(ai.getAndIncrement(u1));
System.out.println(u1.getAge());
}
static class User {
private String name;
public volatile int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
代码详解
要想原子地更新字段类需要两步。第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新类的字段必须使用 public volatile 修饰。
输出结果
18
19
JDK8新增原子类简介
- DoubleAccumulator
- LongAccumulator
- DoubleAdder
- LongAdder
下面以 LongAdder 为例介绍一下,并列出使用注意事项
这些类对应把 AtomicLong 等类的改进。比如 LongAccumulator 与 LongAdder 在高并发环境下比 AtomicLong 更高效。
Atomic、Adder在低并发环境下,两者性能很相似。但在高并发环境下,Adder 有着明显更高的吞吐量,但是有着更高的空间复杂度。
LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator。
sum() 方法在没有并发的情况下调用,如果在并发情况下使用会存在计数不准,下面有代码为例。
LongAdder不可以代替AtomicLong ,虽然 LongAdder 的 add() 方法可以原子性操作,但是并没有使用 Unsafe 的CAS算法,只是使用了CAS的思想。
LongAdder其实是LongAccumulator的一个特例,调用LongAdder相当使用下面的方式调用LongAccumulator,LongAccumulator提供了比LongAdder更强大的功能,构造函数其中accumulatorFunction一个双目运算器接口,根据输入的两个参数返回一个计算值,identity则是LongAccumulator累加器的初始值。
private static ExecutorService executorService = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
counter.add(2);
}
});
}
System.out.println(counter.sum());
System.out.println(counter);
}
输出结果
图片解释
如图LongAdder则是内部维护多个变量,每个变量初始化都0,在同等并发量的情况下,争夺单个变量的线程量会减少这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后获取当前值时候是把所有变量的值累加后返回的。
//构造函数
LongAdder()
//创建初始和为零的新加法器。
//方法摘要
void add(long x) //添加给定的值。
void decrement() //相当于add(-1)。
double doubleValue() //在扩展原始转换之后返回sum()as double。
float floatValue() //在扩展原始转换之后返回sum()as float。
void increment() //相当于add(1)。
int intValue() //返回sum()作为int一个基本收缩转换之后。
long longValue() //相当于sum()。
void reset() //重置将总和保持为零的变量。
long sum() //返回当前的总和。
long sumThenReset() //等同于sum()后面的效果reset()。
String toString() //返回。的字符串表示形式sum()。