它还提供了lasySet
方法,让你以普通变量而非volitile来操作数据,请读者自行了解。
除了基本数据类型有对应的原子类以外,基本的数组类型也有原子类。
演示如下
public class Demo27 { public static void main(String[] args) throws InterruptedException { AtomicIntegerArray array = new AtomicIntegerArray(new int[]{0,3,5,9}); Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 1000000; i++) { array.getAndAdd(0,1); } System.out.println("thread finish..."); } }; new Thread(r).start(); new Thread(r).start(); TimeUnit.SECONDS.sleep(1); System.out.println(array.get(0)); } }
在jdk1.8以后,juc新增了LongAdder
与DoubleAdder
,在高并发场景下,它的性能比AtomicLong
,AtomicDouble
会更好。
它的原理简单介绍如下。如下图,如果是多个线程对atomicLong进行操作,每次只能有一个线程成功CAS,而其它线程都会循环进行CAS直到成功。这样线程等待时间会随着等待队列变长而增加,时间性能不佳。但是LongAdder会自己维护一个cell[]数组,不同的线程都可以操作数组中的不同元素进行CAS,最后再进行求和累加,一次性更新value
使用实例如下
public class Demo28 { public static void main(String[] args) throws InterruptedException { LongAdder integer = new LongAdder(); Runnable r = new Runnable() { @Override public void run() { for (int i = 0; i < 1000000; i++) { integer.add(1); } } }; for (int i = 0; i < 100; i++) { new Thread(r).start(); } TimeUnit.SECONDS.sleep(1); System.out.println(integer.sum()); } }
除了对于基本数据类型有原子操作的支持外,对于引用类型,也可以实现原子操作
public class Demo29 { public static void main(String[] args) { String a = "hello a"; String b = "hello b"; AtomicReference<String> reference = new AtomicReference<>(a); reference.compareAndSet(a, b); System.out.println(reference.get()); } }
juc还提供了字段原子更新器,我们可以对于类中的某个字段进行原子的更新操作(注意字段必须使用vilotile
关键字修饰)
public class Demo30 { public static void main(String[] args) { Student student = new Student(); AtomicIntegerFieldUpdater<Student> updater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age"); System.out.println(updater.incrementAndGet(student)); } public static class Student{ volatile int age; } }
到目前为止,有关原子类的相关介绍结束了。现在我们想象下面的场景线程1,2同时CAS修改变量a的值,线程1速度较快,将a修改为2以后又把a修改为了1,这时线程2才开始判断,发现a的值就是expect的期望值1,于是CAS成功,将变量a修改为了2.很明显,这个时候的1与初始的1不是同一个1了,对于基本数据类型可能还不算太坏,但是对于string等这可不妙,这其实是CAS操作的一个问题,它只会机械的比较当前值是否与期望值一致,并不能知道当前值是否被修改过。这种问题就被称为ABA问题。
如何解决ABA问题呢,juc提供了带版本号的CAS操作,只要每次操作记录下版本号,并且版本号不重复就可以了
public class Demo31 { public static void main(String[] args) { String a = "hello a"; String b = "hello a"; AtomicStampedReference<String> reference = new AtomicStampedReference<>(a, 1); reference.attemptStamp(a,2); System.out.println(reference.compareAndSet(a, b, 2, 3)); } }