atomic包中的类都是通过unsafe的CAS操作实现的线程安全的工具类。CAS指的是compareAndSet(),它是又CPU指令实现的原子操作。
AtomicBoolean
AtomicBoolean是一个读和写都是原子性的boolean类型的变量。
下面是使用compareAndSet()的例子:
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
boolean expectedValue = true;
boolean newValue = false;
boolean wasNewValueSet = atomicBoolean.compareAndSet(
expectedValue, newValue);
AtomicInteger、AtomicLong
这两个类是对基本数据类型的并发安全操作的类,能够实现线程安全的自增操作,同时它的实现也是通过CAS来完成。
LongAdder、DoubleAdder
LongAdder是jdk8新增的用于并发环境的计数器,目的是为了在高并发情况下,代替AtomicLong/AtomicInt,成为一个用于高并发情况下的高效的通用计数器。高并发下计数,一般最先想到的应该是AtomicLong/AtomicInt,AtmoicXXX使用硬件级别的指令 CAS 来更新计数器的值,这样可以避免加锁,机器直接支持的指令,效率也很高。
但是AtomicXXX中的 CAS 操作在出现线程竞争时,失败的线程会白白地循环一次,在并发很大的情况下,因为每次CAS都只有一个线程能成功,竞争失败的线程会非常多。失败次数越多,循环次数就越多,很多线程的CAS操作越来越接近自旋锁(spin lock)。计数操作本来是一个很简单的操作,实际需要耗费的cpu时间应该是越少越好,AtomicXXX在高并发计数时,大量的cpu时间都浪费会在自旋上了,这很浪费,也降低了实际的计数效率。
说LongAdder比在高并发时比AtomicLong更高效,这么说有什么依据呢?LongAdder是根据ConcurrentHashMap这类为并发设计的类的基本原理——锁分段,来实现的,它里面维护一组按需分配的计数单元,并发计数时,不同的线程可以在不同的计数单元上进行计数,这样减少了线程竞争,提高了并发效率。本质上是用空间换时间的思想,不过在实际高并发情况中消耗的空间可以忽略不计。
现在,在处理高并发计数时,应该优先使用LongAdder,而不是继续使用AtomicLong。当然,线程竞争很低的情况下进行计数,使用Atomic还是更简单更直接,并且效率稍微高一些。其他情况,比如序号生成,这种情况下需要准确的数值,全局唯一的AtomicLong才是正确的选择,此时不应该使用LongAdder。
实现原理:对于多个线程对同一个变量操作不相互排斥,而是保存每个线程对变量的修改,在读取变量的时候进行汇总,这样就不会造成线程间的互斥和重试,极大提高想并发性能。
AtomicReference
AtomicReference类提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。AtomicReferenc的compareAndSet()方法可以使得它与期望的一个值进行比较,如果他们是相等的,AtomicReference里的对象会被设置成一个新的引用。
下面是AtomicReference compareAndSet()方法的例子:
String initialReference = "initial value referenced";
AtomicReference<String> atomicStringReference =
new AtomicReference<String>(initialReference);
String newReference = "new value referenced";
boolean exchanged = atomicStringReference.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
exchanged = atomicStringReference.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
AtomicReferenceFieldUpdater
基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。该类用于原子数据结构,该结构中同一节点的几个引用字段都独立受原子更新控制。
简单的示例:
public class App {
public static void main(String[] args) throws Exception {
AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dog.class,String.class,"name");
Dog dog1=new Dog();
updater.compareAndSet(dog1,dog1.name,"test") ;
System.out.println(dog1.name);
}
}
class Dog {
volatile String name="dog1";
}
AtomicStampedReference
这个类用于解决CAS中的ABA问题。它内部不仅维护了对象值,还维护了一个版本号(我这里把它称为版本号,实际上它可以使任何一个整数,它使用整数来表示状态值)。当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新版本号。当AtomicStampedReference设置对象值时,对象值以及版本号都必须满足期望值,写入才会成功。因此,即使对象值被反复读写,写回原值,只要版本号发生变化,就能防止不恰当的写入。
public static void main(String[] args) {
String str1 = "aaa";
String str2 = "bbb";
AtomicStampedReference<String> reference = new AtomicStampedReference<String>(str1,1);
reference.compareAndSet(str1,str2,reference.getStamp(),reference.getStamp()+1);
System.out.println("reference.getReference() = " + reference.getReference());
boolean b = reference.attemptStamp(str2, reference.getStamp() + 1);
System.out.println("b: "+b);
System.out.println("reference.getStamp() = "+reference.getStamp());
boolean c = reference.weakCompareAndSet(str2,"ccc",4, reference.getStamp()+1);
System.out.println("reference.getReference() = "+reference.getReference());
System.out.println("c = " + c);
}
输出:
reference.getReference() = bbb
b: true
reference.getStamp() = 3
reference.getReference() = bbb
c = false
c为什么输出false呢, 因为版本戳不一致啦
AtomicLongArray
内部维护了一个数组,提供方法能够CAS更改数组的某个下标的值。
总结
atomic包中的类都是通过CAS的操作完成线程安全的更新值。了解这些类可以在需要的时候进行使用。