emmmm,在写文章前我也翻阅了好多资料和书籍,其实大家在对原子类方法的使用介绍基本都没问题,但是对于java中原子类的个数是五花八门,下面我就把自己都认知和书籍资料结合起来给大家简单都介绍下java中原子类的应用。
参考文献
《Java并发编程的艺术》
在JDK7包括7之前,java原子类有12个,图片如下,有些资料说有13个,多出来的是 AtomicBooleanArray 类,可是我在JDK8之前的源码里并没有发现有这个类,当然我也没去8以上的版本去看,所以这里不确定这个类到底在哪个版本中存在。
在JDK8时出现了4个原子操作类,分别是如下图片所示
使用原子的方式更新基本类型,Atomic包提供了以下3个类。
以上3个类提供的方法几乎一模一样,以AtomicInteger为例进行详解,AtomicIngeter的常用方法如下:
代码示例
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个,笔者水平有限,真的没有发现第四个,如果哪位大佬知道的话,可以下方留言区指出来。
这三个类的最常用的方法是如下两个方法:
下面以 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包提供了以下三个类:
public static AtomicReference ai = new AtomicReference();
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个类进行原子字段更新:
//创建原子更新器,并设置需要更新的对象类和对象的属性
private static AtomicIntegerFieldUpdater 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
下面以 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()。
关于更多jdk8新增原子类的学习,推荐看下面几个文章,我也是跟着他们学的。
https://blog.csdn.net/zhailuxu/article/details/79073469
https://blog.csdn.net/yao123long/article/details/63683991
https://blog.csdn.net/panweiwei1994/article/details/78739264
https://blog.csdn.net/zhailuxu/article/details/79073473?utm_source=blogxgwz9
感兴趣可以加微信,带你去大佬云集的交流群