引言
Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操做。底层使用了Unsafe类的native方法,提供了硬件级别的原子操做,可是不一样的CPU架构可能提供的原子指令不同,也有可能须要某种形式的内部锁,因此该包下的方法不能绝对保证线程不被阻塞。java
Atomic包介绍
在Atomic包里一共有17个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe的CAS实现的包装类。程序员
原子更新基本类型类
用于经过原子的方式更新基本类型,Atomic包提供了如下三个类:数组
AtomicBoolean:原子更新布尔类型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新长整型。
AtomicInteger的经常使用方法以下:安全
int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
boolean compareAndSet(int expect, int update) :若是输入的数值等于预期值,则以原子方式将该值设置为输入的值。
int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能致使其余线程在以后的一小段时间内仍是能够读到旧的值。关于该方法的更多信息能够参考并发网翻译的一篇文章《AtomicLong.lazySet是如何工做的?》
int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
AtomicInteger例子代码以下:多线程
import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { static AtomicInteger ai = new AtomicInteger(1); public static void main(String[] args) { System.out.println(ai.getAndIncrement()); System.out.println(ai.get()); } }
输出架构
1 2
餐后甜点
Atomic包提供了三种基本类型的原子更新,可是Java的基本类型里还有char,float和double等。那么问题来了,如何原子的更新其余的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,让咱们一块儿看下Unsafe的源码,发现Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,因此原子更新double也能够用相似的思路来实现。并发
原子更新数组类
经过原子的方式更新数组里的某个元素,Atomic包提供了如下三个类:函数
AtomicIntegerArray:原子更新整型数组里的元素。
AtomicLongArray:原子更新长整型数组里的元素。
AtomicReferenceArray:原子更新引用类型数组里的元素。
AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其经常使用方法以下高并发
int addAndGet(int i, int delta):以原子方式将输入值与数组中索引i的元素相加。
boolean compareAndSet(int i, int expect, int update):若是当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。
实例代码以下:性能
public class AtomicIntegerArrayTest { static int[] value = new int[] { 1, 2 }; static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args) { ai.getAndSet(0, 3); System.out.println(ai.get(0)); System.out.println(value[0]); } }
输出
3 1
AtomicIntegerArray类须要注意的是,数组value经过构造方法传递进去,而后AtomicIntegerArray会将当前数组复制一份,因此当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。
原子更新引用类型
原子更新基本类型的AtomicInteger,只能更新一个变量,若是要原子的更新多个变量,就须要使用这个原子更新引用类型提供的类。Atomic包提供了如下三个类:
AtomicReference:原子更新引用类型。
AtomicReference的使用例子代码以下:
import java.util.concurrent.atomic.AtomicReference; import static java.lang.System.out; public class AtomicReferenceTest { public static void main(String[] args) { AtomicReference<User> atomicUserRef = new AtomicReference(); User user = new User("last", 15); atomicUserRef.set(user); User updateUser = new User("soul", 17); atomicUserRef.compareAndSet(user, updateUser); out.printf("name:%s,age:%d \n",atomicUserRef.get().getName(),atomicUserRef.get().getAge()); } 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 int getAge() { return age; } } }
输出
name:soul,age:17
原子更新字段类
若是咱们只须要某个类里的某个字段,那么就须要使用原子更新字段类AtomicReferenceFieldUpdater。
要想使用原子更新字段类须要注意如下问题:
AtomicReferenceFieldUpdater不能更新原子类型属性,原子类型属性要用AtomicIntegerFieldUpdater,它要注意的问题同上,除了第二条对属性的限制。它要求属性必须是int,而不能是Integer.
和AtomicIntegerFieldUpdater相似的是AtomicLongFieldUpdater,它们是原子更新基本类型字段的更新器。
示例代码以下:
public class FieldUpdaterTest { public static void main(String[] args) { //定义一个更新器,指定更新的对象,字段类型以及字段名字 AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(User.class, Integer.class, "age"); //要被更新的对象 User user1 = new User("user1", 1); //更新对象的属性 updater.compareAndSet(user1, user1.age, 3); System.out.println(user1.age); //另外一个要被更新的对象 User user2 = new User("user2", 1); //更新对象的属性 updater.compareAndSet(user2, user2.age, 4); System.out.println(user2.age); //Integer类型属性更新器,它的属性必须是原子类型,而不能是包装类型 AtomicIntegerFieldUpdater integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "old"); //要被更新的对象 User user3 = new User("user3", 1, 4); //更新对象的属性 integerFieldUpdater.compareAndSet(user3, user3.old, 5); System.out.println(user3.old); } static class User { public volatile String userName; public volatile Integer age; public volatile int old; public User(String userName, int age) { this.userName = userName; this.age = age; } public User(String userName, int age, int old) { this.userName = userName; this.age = age; this.old = old; } } }
输出
10 11
AtomicReference经过volatile和Unsafe提供的CAS函数实现原子操做。 自旋+CAS的无锁操做保证共享变量的线程安全。
可是CAS操做可能存在ABA问题。AtomicStampedReference和AtomicMarkableReference的出现就是为了解决这问题。
AtomicStampedReference
构造方法中initialStamp(时间戳)用来惟一标识引用变量,在构造器内部,实例化了一个Pair对象,Pair对象记录了对象引用和时间戳信息,采用int做为时间戳,实际使用的时候,要保证时间戳惟一(通常作成自增的),若是时间戳若是重复,还会出现ABA的问题。
AtomicStampedReference中的每个引用变量都带上了pair.stamp这个时间戳,这样就能够解决CAS中的ABA的问题。
示例代码:
public class StampedReferenceTest { public static void main(String[] args) throws InterruptedException { //初始值,也能够用自定义对象 int initialRef = 1; //初始版本 int initialStamp = 1; AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(initialRef, initialStamp); System.out.println("initial ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp()); //指望值 final int expectedRef = atomicStampedReference.getReference(); //新值 int newRef = atomicStampedReference.getReference() + 10; //指望版本 final int expectedStamp = atomicStampedReference.getStamp(); //新版本 int newStamp = atomicStampedReference.getStamp() + 1; Thread t1 = new Thread(() -> System.out.println("success:" + atomicStampedReference.compareAndSet(expectedRef, newRef, expectedStamp, newStamp))); t1.start(); t1.join(); System.out.println("ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp()); Thread t2 = new Thread(() -> System.out.println("success:" + atomicStampedReference.compareAndSet(expectedRef, newRef, expectedStamp, newStamp))); t2.start(); t2.join(); System.out.println("ref=" + atomicStampedReference.getReference() + " and stamp=" + atomicStampedReference.getStamp()); } }
AtomicMarkableReference
AtomicStampedReference能够知道,引用变量中途被更改了几回。有时候,咱们并不关心引用变量更改了几回,只是单纯的关心是否更改过,因此就有了AtomicMarkableReference。
AtomicMarkableReference的惟一区别就是再也不用int标识引用,而是使用boolean变量——表示引用变量是否被更改过。
示例代码
public class AtomicMarkableReferenceTest { public static void main(String[] args) throws InterruptedException { //初始值,也能够用自定义对象 int initialRef = 1; //初始标识 boolean initialMark = false; AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference(initialRef, initialMark); System.out.println("initial ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked()); //指望值 int expectedRef = markableReference.getReference(); /** * 并发过程当中t1,t2两个线程的指望值都是expectedRef,把值加10 * t1,t2两个线程的指望标志都是false,操做成功后置为true * t1成功后标志变为true,因此与t2指望值不符,操做失败 */ Thread t1 = new Thread(() -> System.out.println("success:" + markableReference.compareAndSet(expectedRef, markableReference.getReference() + 10, false, true))); t1.start(); t1.join(); System.out.println("ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked()); Thread t2 = new Thread(() -> System.out.println("success:" + markableReference.compareAndSet(expectedRef, markableReference.getReference() + 10, false, false))); t2.start(); t2.join(); System.out.println("ref=" + markableReference.getReference() + " and isMarked=" + markableReference.isMarked()); } }
DoubleAccumulator、LongAccumulator、DoubleAdder、LongAdder是JDK1.8新增的部分,是对AtomicLong等类的改进。好比LongAccumulator与LongAdder在高并发环境下比AtomicLong更高效。本文以LongAdder为例,学习这些类。
API中是这么介绍的:LongAdder中会维护一组(一个或多个)变量,这些变量加起来就是要以原子方式更新的long型变量。当更新方法add(long)在线程间竞争时,该组变量能够动态增加以减缓竞争。方法sum()返回当前在维持总和的变量上的总和。与AtomicLong相比,LongAdder更多地用于收集统计数据,而不是细粒度的同步控制。在低并发环境下,二者性能很类似。但在高并发环境下,LongAdder有着明显更高的吞吐量,可是有着更高的空间复杂度。
实现原理
与其余原子类同样,LongAdder也是基于CAS实现的。
LongAdder能够代替AtomicLong吗
固然不能。在上面已经提到,与AtomicLong相比,LongAdder更多地用于收集统计数据,而不是细粒度的同步控制。并且,LongAdder只提供了add(long)和decrement()方法,想要使用cas方法仍是要选择AtomicLong。
DoubleAdder、LongAccumulator、DoubleAccumulator与LongAdder很类似,就很少作介绍了。