2019年5月12日 -- 原子类
原子类
在并发编程中需要考虑线程安全的问题,比如:多线程同时对变量 a 执行 a++操作,使用Synchronized可以达到线程安全的目的,但是synchronized采用的是悲观锁策略,并不是特别高效的一种解决方案。JDK1.5开始,在java.util.concurrent.atomic 包中提供了一系列操作简单、性能高效、并能保证线程安全的原子类,这些类都是采用乐观锁策略去原子更新数据,具体实现是基于CAS操作。
什么是CAS?
CAS的全称是Compare-And-Swap,意思是比较后交换。CAS比较交换的过程可以通俗的理解为CAS(V,O,N),包含三个值分别为:V 内存地址存放的实际值;O 预期的值(旧值);N 更新的新值。当V和O相同时,也就是线程工作内存缓存的值和主内存中实际的值相同,可以将新值N赋值给V。反之,V和O不相同则表明该值已经被其它线程改过了,所以不能将新值N赋给V。
CAS的底层实现需要硬件指令集的支撑,在JDK1.5后虚拟机才可以使用处理器提供的CMPXCHG指令实现。
CAS的问题
ABA问题
比如一个旧值A变成了B,然后又变成了A,这时CAS检查会发现旧值并没有变化,依然为A,但是实际上的确发生了变化,这就是ABA问题。原子类AtomicStampedReference 可以解决 ABA 问题,内部原理就是通过添加版本号,使原来的变化路径A->B->A变成了1A->2B->3A。
自旋时间过长
CAS是非阻塞同步,也就是说不会将线程挂起,会自旋(死循环)进行下一次尝试,如果自旋时间过长,对性能是很大的消耗。如果 JVM 能支持处理器提供的 pause 指令,那么效率会有一定的提升。pause 指令有两个作用,第一可以延迟流水线执行指令(de-pipeline),使 CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起 CPU 流水线被清空(CPU pipeline flush),从而提高 CPU 的执行效率。
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时,循环 CAS 就无法保证操作的原子性。原子类AtomicReference 可以把多个变量放在一个对象里来进行CAS操作,从而保证引用对象的原子性。
原子类的分类
按照原子更新方式,这些原子操作类大致可以分为四种:原子更新基本类型、原子更新数组、原子更新引用类型、原子更新对象属性。
基本数据类型原子类
基本数据类型原子类主要为以下几种:
- AtomicBoolen: boolean类型原子类,可以用于标记。
- AtomicInteger: int类型原子类,可以用于多线程计数器。
- AtomicLong: long类型原子类
Atomic包下提供的更新基本类型变量的原子类源码大致一致,有兴趣的读者可以自行阅读。这里我们以AtomicInteger来进行讲解,具体代码如下:
AtomicInteger jdk1.8源码
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } /** * Creates a new AtomicInteger with initial value {@code 0}. */ public AtomicInteger() { }/** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * <p><a href="package-summary.html#weakCompareAndSet">May fail * spuriously and does not provide ordering guarantees</a>, so is * only rarely an appropriate alternative to {@code compareAndSet}. * * @param expect the expected value * @param update the new value * @return {@code true} if successful */ public final boolean weakCompareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } /** * Atomically increments by one the current value. * i++ * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } /** * Atomically decrements by one the current value. * i-- * @return the previous value */ public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the previous value */ public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } /** * Atomically increments by one the current value. * ++i * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } /** * Atomically decrements by one the current value. * --i * @return the updated value */ public final int decrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, -1) - 1; } /** * Atomically adds the given value to the current value. * * @param delta the value to add * @return the updated value */ public final int addAndGet(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta) + delta; }
基于CAS
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; }
Example:使用AtomicInteger类型实现多线程安全的计数器。
public class OneWrapper { public static void main(String[] args) { // AtomicInteger 实现计数器 AtomicInteger count = new AtomicInteger(0); for (int i = 0; i < 10; ++i) { CountThread thread = new CountThread(count); thread.start(); try { // join 的含义:等待该线程终止。就是:当前线程等待子线程的终止。 thread.join(0); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("count: " + count.get()); } } class CountThread extends Thread{ private AtomicInteger integer; CountThread(AtomicInteger integer){ this.integer = integer; } @Override public void run(){ integer.addAndGet(1); System.out.println("count thread running!"); } }
注意:AtomicBoolean的value的类型并不是布尔型,而是整型,它会把对应的布尔值转换成整型,true的时候对应1,false的时候对应0。
为什么是整型?看源码会发现,Unsafe提供的CompareAndSwap方法不支持boolean。
数组类型原子类
对于数组类型的原子类,在Java中,主要是通过原子的方式更新数组里面的某个元素,数组类型原子类主要有以下几种:
- AtomicIntegerArray:Int数组类型原子类
- AtomicLongArray:long数组类型原子类
- AtomicReferenceArray:引用类型原子类(关于AtomicReferenceArray即引用类型原子类会在下文介绍)
AtomicIntegerArray与AtomicInteger的方法基本一致,只不过在AtomicIntegerArray的方法中会多一个指定数组元素的索引位。AtomicIntegerArray自己维护一个整型数组array,对数组元素的操作实质是对array的操作。
AtomicIntegerArray jdk1.8源码
public class AtomicIntegerArray implements java.io.Serializable { private static final long serialVersionUID = 2862133569453604235L; private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final int base = unsafe.arrayBaseOffset(int[].class); private static final int shift; private final int[] array; static { int scale = unsafe.arrayIndexScale(int[].class); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); shift = 31 - Integer.numberOfLeadingZeros(scale); } private long checkedByteOffset(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return byteOffset(i); } private static long byteOffset(int i) { return ((long) i << shift) + base; } /** * Creates a new AtomicIntegerArray of the given length, with all * elements initially zero. * * @param length the length of the array */ public AtomicIntegerArray(int length) { array = new int[length]; } /** * Creates a new AtomicIntegerArray with the same length as, and * all elements copied from, the given array. * * @param array the array to copy elements from * @throws NullPointerException if array is null */ public AtomicIntegerArray(int[] array) { // Visibility guaranteed by final field guarantees this.array = array.clone(); } /** * Returns the length of the array. * * @return the length of the array */ public final int length() { return array.length; } /** * Gets the current value at position {@code i}. * * @param i the index * @return the current value */ public final int get(int i) { return getRaw(checkedByteOffset(i)); }/** * Atomically sets the element at position {@code i} to the given * updated value if the current value {@code ==} the expected value. * * <p><a href="package-summary.html#weakCompareAndSet">May fail * spuriously and does not provide ordering guarantees</a>, so is * only rarely an appropriate alternative to {@code compareAndSet}. * * @param i the index * @param expect the expected value * @param update the new value * @return {@code true} if successful */ public final boolean weakCompareAndSet(int i, int expect, int update) { return compareAndSet(i, expect, update); } /** * Atomically increments by one the element at index {@code i}. * * @param i the index * @return the previous value */ public final int getAndIncrement(int i) { return getAndAdd(i, 1); } /** * Atomically decrements by one the element at index {@code i}. * * @param i the index * @return the previous value */ public final int getAndDecrement(int i) { return getAndAdd(i, -1); } /** * Atomically adds the given value to the element at index {@code i}. * * @param i the index * @param delta the value to add * @return the previous value */ public final int getAndAdd(int i, int delta) { return unsafe.getAndAddInt(array, checkedByteOffset(i), delta); } /** * Atomically increments by one the element at index {@code i}. * * @param i the index * @return the updated value */ public final int incrementAndGet(int i) { return getAndAdd(i, 1) + 1; } /** * Atomically decrements by one the element at index {@code i}. * * @param i the index * @return the updated value */ public final int decrementAndGet(int i) { return getAndAdd(i, -1) - 1; } /** * Atomically adds the given value to the element at index {@code i}. * * @param i the index * @param delta the value to add * @return the updated value */ public final int addAndGet(int i, int delta) { return getAndAdd(i, delta) + delta; }
Example:多线程同时修改数组中的某个元素。
public class TwoArray { public static void main(String[] args) { int[] a = {2,4,5}; // 这里初始化对数组a的使用是深拷贝,在原子类中修改数组的元素值,不会影响到原有数组的值 AtomicIntegerArray array = new AtomicIntegerArray(a); // 多个线程同时数组中下标为1的元素值 int index = 1; for (int i = 0; i < 2; ++i){ ArrayThread thread = new ArrayThread(array, index); thread.start(); try { thread.join(); } catch (InterruptedException ex){ ex.printStackTrace(); } } System.out.println(array.get(index)); System.out.println(a[index]); } } class ArrayThread extends Thread{ AtomicIntegerArray array; int index; public ArrayThread (AtomicIntegerArray array,int index){ this.array = array; this.index = index; } @Override public void run(){ for (int i = 0; i < 5; ++i){ array.addAndGet(index, 1); } } }
引用类型原子类
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。
引用类型的主要原子类如下:
- AtomicReference:
- AtomicMarkableReference:用于更新带有标记位的引用类型。
- AtomicStampedReference:用于更新带有版本号的引用类型,该类将版本号与引用类型关联起来,可以解决使用CAS进行原子更新时可能会出现的ABA问题。
关于引用类型的原子类,内部都调用的是CompareAndSwapObject()方法来实现CAS操作的。
AtomicJDK1.8源码
public class AtomicReference<V> implements java.io.Serializable { private static final long serialVersionUID = -1848883965231344442L; private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile V value; /** * Creates a new AtomicReference with the given initial value. * * @param initialValue the initial value */ public AtomicReference(V initialValue) { value = initialValue; } /** * Creates a new AtomicReference with null initial value. */ public AtomicReference() { } /** * Gets the current value. * * @return the current value */ public final V get() { return value; } /** * Sets to the given value. * * @param newValue the new value */ public final void set(V newValue) { value = newValue; } /** * Eventually sets to the given value. * * @param newValue the new value * @since 1.6 */ public final void lazySet(V newValue) { unsafe.putOrderedObject(this, valueOffset, newValue); } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * @param expect the expected value * @param update the new value * @return {@code true} if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * <p><a href="package-summary.html#weakCompareAndSet">May fail * spuriously and does not provide ordering guarantees</a>, so is * only rarely an appropriate alternative to {@code compareAndSet}. * * @param expect the expected value * @param update the new value * @return {@code true} if successful */ public final boolean weakCompareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } /** * Atomically sets to the given value and returns the old value. * * @param newValue the new value * @return the previous value */ @SuppressWarnings("unchecked") public final V getAndSet(V newValue) { return (V)unsafe.getAndSetObject(this, valueOffset, newValue); } /** * Atomically updates the current value with the results of * applying the given function, returning the previous value. The * function should be side-effect-free, since it may be re-applied * when attempted updates fail due to contention among threads. * * @param updateFunction a side-effect-free function * @return the previous value * @since 1.8 */ public final V getAndUpdate(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return prev; } /** * Atomically updates the current value with the results of * applying the given function, returning the updated value. The * function should be side-effect-free, since it may be re-applied * when attempted updates fail due to contention among threads. * * @param updateFunction a side-effect-free function * @return the updated value * @since 1.8 */ public final V updateAndGet(UnaryOperator<V> updateFunction) { V prev, next; do { prev = get(); next = updateFunction.apply(prev); } while (!compareAndSet(prev, next)); return next; }
Example:多线程同时修改对象
public class ThreeReference {
public static void main(String[] args) {
AtomicReference<User> atomicReference = new AtomicReference<>();
User user = new User("uu", "beijing");
for (int i = 0; i < 5; ++i){
ReferenceThread thread = new ReferenceThread(atomicReference,user,i);
thread.start();
try {
thread.join();
} catch (InterruptedException ex){
ex.printStackTrace();
}
}
System.out.println(atomicReference.get());
}
}
class ReferenceThread extends Thread{
AtomicReference<User> atomicReference;
User user;
int i;
public ReferenceThread(AtomicReference<User> atomicReference, User user,int i){
this.atomicReference = atomicReference;
this.user = user;
this.i = i;
}
@Override
public void run(){
user.setAddress("beijing" + i);
user.setUsername("uu" + i);
atomicReference.set(user);
//System.out.println(user.toString());
}
}
class User{
private String username;
private String address;
public User(String username,String address){
this.username = username;
this.address = address;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
}
原子更新对象属性
如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:
- AtomicIntegeFieldUpdater:原子更新整型字段类;
- AtomicLongFieldUpdater:原子更新长整型字段类;
- AtomicReferenceFieldUpdater:原子更新引用类型,这种更新方式会带有版本号,为了解决CAS的ABA问题;
要想使用原子更新字段需要两步操作
- 原子更新字段类都是抽象类,只能通过静态方法
newUpdater来创建一个更新器,并且需要设置想要更新的类和属性; - 更新类的属性必须使用
public volatile进行修饰;

浙公网安备 33010602011771号