引言
欢迎大家来到并发编程第四期-Atomic原子类详解,在之前我们学习了JMM,ConcurrentHashMap,线程 池,现在我们来学习一个并发工具类-Atomic。学完了这篇文章,对你并发编程的工具使用会有很大的提升。
什么是Atomic原子类
大家都知道并发编程中会有线程安全问题,至于线程安全我例子就不说了。解决方法最常见的就是加Synchronized锁,在这之前我们也介绍过,这是一个重量级锁,也是一个悲观锁,由于CPU不停的自旋,就会导致性能的损耗,那么我们就需要更好的解决方案。这时Atomic横空出世。
在JUC下有一个Atomic原子类,操作简单,性能高效,而且能保证线程安全的同时做到效率高。实际上,效率高的原因是Atomic锁机制是乐观锁,底层使用了CAS。
java.util.concurrent.atomic下的一组原子操作类
基本类型:AtomicInteger、AtomicLong、AtomicBoolean;
引用类型:AtomicReference、AtomicStampedRerence、AtomicMarkableReference;
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
对象属性原子修改器:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、 AtomicReferenceFieldUpdater
原子类型累加器(jdk1.8增加的类):DoubleAccumulator、DoubleAdder、 LongAccumulator、LongAdder、Striped64
原子更新基本类型
以AtomicInteger常用方法举例
Java
// 以原子的方式将实例中的原值加1,返回的是自增前的旧值;
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// getAndSet(int newValue):将实例中的值更新为新值,并返回旧值;
public final boolean getAndSet(boolean newValue) {
boolean prev;
do {
prev = get();
} while (!compareAndSet(prev, newValue));
return prev;
}
// incrementAndGet() :以原子的方式将实例中的原值进行加1操作,并返回最终相加后的结果;
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// addAndGet(int delta) :以原子方式将输入的数值与实例中原本的值相加,并返回最后的结果;
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
测试代码
Java
public class AtomicIntegerTest {
static AtomicInteger sum = new AtomicInteger(0);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
// 原子自增 CAS
sum.incrementAndGet();
//TODO
}
});
thread.start();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(sum.get());
}
}
测试结果
大家都知道如果直接100000个线程去加同一个变量,那么肯定会线程不安全,最后得到的结果肯定不是100000,但是通过这个线程的工具类就保证了线程安全。这是因为底层是通过CAS原子指令操作的,至于什么是CAS,简单来说,它不是通过加锁来控制变量修改的,他是通过前后变量的比较,再通过偏移量来确定是否是上一个变量,如果是,那么就可以修改,如果不是,就修改失败,证明已经有其他线程进行修改了,而这一个线程已经晚了,所有保证了线程安全。
缺点 但是,CAS是通过CPU的不断自旋来确定是否是这个变量,所以这就很消耗CPU性能,当很多请求时,就会造成服务器的资源耗尽
原子类更新数组内容
java
//addAndGet(int i, int delta):以原子更新的方式将数组中索引为i的元素与输入值相加;
public final int addAndGet(int i, int delta) {
return getAndAdd(i, delta) + delta;
}
//getAndIncrement(int i):以原子更新的方式将数组中索引为i的元素自增加1;
public final int getAndIncrement(int i) {
return getAndAdd(i, 1);
}
//compareAndSet(int i, int expect, int update):将数组中索引为i的位置的元素进行更新
public final boolean compareAndSet(int i, int expect, int update) {
return compareAndSetRaw(checkedByteOffset(i), expect, update);
}
测试原子类更新数组
java
public class AtomicIntegerArrayTest {
static int[] value = new int[]{ 1, 2, 3, 4, 5 };
static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(value);
public static void main(String[] args) throws InterruptedException {
//设置索引0的元素为100
atomicIntegerArray.set(0, 100);
System.out.println(atomicIntegerArray.get(0));
//以原子更新的方式将数组中索引为1的元素与输入值相加
atomicIntegerArray.getAndAdd(1, 5);
System.out.println(atomicIntegerArray);
}
}
测试结果:
原子类更新引用类型
java
public class AtomicReferenceTest {
public static void main(String[] args) {
User user1 = new User("张三", 23);
User user2 = new User("李四", 25);
User user3 = new User("王五", 20);
//初始化为 user1
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(user1);
//把 user2 赋给 atomicReference
atomicReference.compareAndSet(user1, user2);
System.out.println(atomicReference.get());
//把 user3 赋给 atomicReference
atomicReference.compareAndSet(user1, user3);
System.out.println(atomicReference.get());
}
}
@Data
@AllArgsConstructor
class User {
private String name;
private Integer age;
}
这段代码把引用类型user1赋值给atomicReference,其中类AtomicReference是一个泛型类,这说明这个类可以传入其他类,也是atomic原子类为了更新所有引用类型做的设计。之后把user2,user3线程安全的赋值给atomicReference。