所谓 Atomic
,翻译过来就是原子。原子被认为是操做中最小的单位,一段代码若是是原子的,则表示这段代码在执行过程当中,要么执行成功,要么执行失败。原子操做通常都是底层经过 CPU
的指令来实现。而 atomic
包下的这些类,则可让咱们在多线程环境下,经过一种无锁的原子操做来实现线程安全。html
atomic
包下的类基本上都是借助 Unsafe
类,经过 CAS
操做来封装实现的。Unsafe
这个类不属于 Java
标准,或者说这个类是 Java
预留的一个后门类,JDK
中,有关提高性能的 concurrent
或者 NIO
等操做,大部分都是借助于这个类来封装操做的。 Java
是种编译型语言,不像 C
语言能支持操做内存,正常状况下都是由 JVM
进行内存的建立回收等操做,但这个类提供了一些直接操做内存相关的底层操做,使得咱们也能够手动操做内存,但从类的名字就能够看出,这个类不是安全的,官方也是不建议咱们使用的。java
CAS原理
CAS
包含 3
个参数 CAS(V,E,N)
. V
表示要更新的变量, E
表示预期值, N
表示新值.数据库
仅当V
值等于E
值时, 才会将V
的值设为N
, 若是V
值和E
值不一样, 则说明已经有其余线程作了更新, 则当前线程什么都不作. 最后, CAS
返回当前V
的真实值. CAS
操做是抱着乐观的态度进行的, 它老是认为本身能够成功完成操做.数组
当多个线程同时使用CAS
操做一个变量时, 只有一个会胜出, 并成功更新, 其他均会失败.失败的线程不会被挂起,仅是被告知失败, 而且容许再次尝试, 固然也容许失败的线程放弃操做.基于这样的原理, CAS
操做即时没有锁,也能够发现其余线程对当前线程的干扰, 并进行恰当的处理.安全
在 JDK8
的 atomic
包下,大概有 16
个类,按照原子的更新方式,大概能够分为 4
类:原子更新普通类型,原子更新数组,原子更新引用,原子更新字段。多线程
原子更新普通类型
atomic
包下提供了三种基本类型的原子更新,分别是 AtomicBoolean
,AtomicInteger
,AtomicLong
,这几个原子类对应于基础类型的布尔,整形,长整形,至于 Java
中其余的基本类型,如 float
等,若是须要,能够参考这几个类的源码自行实现。并发
AtomicBoolean
主要接口dom
public final boolean get(); public final boolean compareAndSet(boolean expect, boolean update); public boolean weakCompareAndSet(boolean expect, boolean update); public final void set(boolean newValue); public final void lazySet(boolean newValue); public final boolean getAndSet(boolean newValue);
这里面的操做都很正常,主要都是用到了 CAS
。这个类中的方法很少,基本上上面都介绍了,而内部的计算则是先将布尔转换为数字0/1
,而后再进行后续计算。高并发
AtomicLong
主要接口源码分析
public final long get(); public final void set(long newValue); public final void lazySet(long newValue); public final long getAndSet(long newValue); public final boolean compareAndSet(long expect, long update); public final boolean weakCompareAndSet(long expect, long update); public final long getAndIncrement(); public final long getAndDecrement(); public final long getAndAdd(long delta); public final long incrementAndGet(); public final long decrementAndGet(); public final long addAndGet(long delta); public final long getAndUpdate(LongUnaryOperator updateFunction); public final long updateAndGet(LongUnaryOperator updateFunction);
这个和下面要讲的 AtomicInteger
相似,下面具体说下。
AtomicInteger
主要接口
// 取得当前值 public final int get(); // 设置当前值 public final void set(int newValue); // 设置新值,并返回旧值 public final int getAndSet(int newValue); // 若是当前值为expect,则设置为u public final boolean compareAndSet(int expect, int u); // 当前值加1,返回旧值 public final int getAndIncrement(); // 当前值减1,返回旧值 public final int getAndDecrement(); // 当前值增长delta,返回旧值 public final int getAndAdd(int delta); // 当前值加1,返回新值 public final int incrementAndGet(); // 当前值减1,返回新值 public final int decrementAndGet(); // 当前值增长delta,返回新值 public final int addAndGet(int delta);
实现
// 封装了一个int对其加减 private volatile int value; ....... public final boolean compareAndSet(int expect, int update) { // 经过unsafe 基于CPU的CAS指令来实现, 能够认为无阻塞. return unsafe.compareAndSwapInt(this, valueOffset, expect, update); } ....... public final int getAndIncrement() { for (;;) { // 当前值 int current = get(); // 预期值 int next = current + 1; if (compareAndSet(current, next)) { // 若是加成功了, 则返回当前值 return current; } // 若是加失败了, 说明其余线程已经修改了数据, 与指望不相符, // 则继续无限循环, 直到成功. 这种乐观锁, 理论上只要等两三个时钟周期就能够设值成功 // 相比于直接经过synchronized独占锁的方式操做int, 要大大节约等待时间. } }
用一个简单的例子测试下:
AtomicInteger atomicInteger = new AtomicInteger(1); System.out.println(atomicInteger.incrementAndGet()); // 2 System.out.println(atomicInteger.getAndIncrement()); // 2 System.out.println(atomicInteger.getAndAccumulate(2, (i, j) -> i + j)); // 3 System.out.println(atomicInteger.get()); // 5 System.out.println(atomicInteger.addAndGet(5));
原子更新数组
atomic
包下提供了三种数组相关类型的原子更新,分别是 AtomicIntegerArray
,AtomicLongArray
,AtomicReferenceArray
,对应于整型,长整形,引用类型,要说明的一点是,这里说的更新是指更新数组中的某一个元素的操做。
因为方法和更新基本类型方法相同,这里只简单看下 AtomicIntegerArray
这个类的几个方法,其余的方法相似。
AtomicIntegerArray
主要接口
// 得到数组第i个下标的元素 public final int get(int i); // 得到数组的长度 public final int length(); // 将数组第i个下标设置为newValue,并返回旧的值 public final int getAndSet(int i, int newValue); // 进行CAS操做,若是第i个下标的元素等于expect,则设置为update,设置成功返回true public final boolean compareAndSet(int i, int expect, int update); // 将第i个下标的元素加1 public final int getAndIncrement(int i); // 将第i个下标的元素减1 public final int getAndDecrement(int i); // 将第i个下标的元素增长delta(delta能够是负数) public final int getAndAdd(int i, int delta);
实现
// 数组自己基地址 private static final int base = unsafe.arrayBaseOffset(int[].class); // 封装了一个数组 private final int[] array; static { // 数组中对象的宽度, int类型, 4个字节, scale = 4; int scale = unsafe.arrayIndexScale(int[].class); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); // 前导0 : 一个数字转为二进制后, 他前面0的个数 // 对于4来说, 他就是00000000 00000000 00000000 00000100, 他的前导0 就是29 // 因此shift = 2 shift = 31 - Integer.numberOfLeadingZeros(scale); } // 获取第i个元素 public final int get(int i) { return getRaw(checkedByteOffset(i)); } // 第i个元素, 在数组中的偏移量是多少 private long checkedByteOffset(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return byteOffset(i); } // base : 数组基地址, i << shift, 其实就是i * 4, 由于这边是int array. private static long byteOffset(int i) { // i * 4 + base return ((long) i << shift) + base; } // 根据偏移量从数组中获取数据 private int getRaw(long offset) { return unsafe.getIntVolatile(array, offset); }
用一个简单的例子测试一下:
AtomicIntegerArray array = new AtomicIntegerArray(5); array.set(0, 1); // 设置数组第一个值为1 System.out.println(array.getAndDecrement(0)); // 1 System.out.println(array.addAndGet(0, 5)); // 5
原子更新引用
更新引用类型的原子类包含了AtomicReference
(更新引用类型),AtomicReferenceFieldUpdater
(抽象类,更新引用类型里的字段),AtomicMarkableReference
(更新带有标记的引用类型)这三个类,这几个类能同时更新多个变量。
AtomicReference
与 AtomicInteger
相似, 只是里面封装了一个对象, 而不是 int
, 对引用进行修改。
主要接口
public final V get(); public final void set(V newValue); public final boolean compareAndSet(V expect, V update); public final V getAndSet(V newValue);
测试 使用 10
个线程, 同时尝试修改 AtomicReference
中的 String
, 最终只有一个线程能够成功。
import java.util.concurrent.atomic.AtomicReference; public class AtomicReferenceTest { public final static AtomicReference<String> attxnicStr = new AtomicReference<String>("abc"); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread() { public void run() { try { Thread.sleep(Math.abs((int) (Math.random() * 100))); } catch (InterruptedException e) { e.printStackTrace(); } if (attxnicStr.compareAndSet("abc", "def")) { System.out.println("Thread:" + Thread.currentThread().getId() + " change value to " + attxnicStr.get()); } else { System.out.println("Thread:" + Thread.currentThread().getId() + " change failed!"); } } }.start(); } } }
原子更新字段
若是更新的时候只更新对象中的某一个字段,则可使用 atomic
包提供的更新字段类型:AtomicIntegerFieldUpdater
,AtomicLongFieldUpdater
和 AtomicStampedReference
,前两个顾名思义,就是更新 int
和 long
类型,最后一个是更新引用类型,该类提供了版本号,用于解决经过 CAS
进行原子更新过程当中,可能出现的 ABA
问题。 前面这两个类和上面介绍的 AtomicReferenceFieldUpdater
有些类似,都是抽象类,都须要经过 newUpdater
方法进行实例化,而且对字段的要求也是同样的。
AtomicStampedReference
ABA问题
线程一准备用 CAS
将变量的值由 A
替换为 B
, 在此以前线程二将变量的值由 A
替换为 C
, 线程三又将 C
替换为A
, 而后线程一执行 CAS
时发现变量的值仍然为 A
, 因此线程一 CAS
成功.
主要接口
// 比较设置 参数依次为:指望值 写入新值 指望时间戳 新时间戳 public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) // 得到当前对象引用 public V getReference() // 得到当前时间戳 public int getStamp() // 设置当前对象引用和时间戳 public void set(V newReference, int newStamp)
分析
// 内部封装了一个Pair对象, 每次对对象操做的时候, stamp + 1 private static class Pair<T> { final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) { return new Pair<T>(reference, stamp); } } private volatile Pair<V> pair; // 进行cas操做的时候, 会对比stamp的值 public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
测试
要求:后台使用多个线程对用户充值, 要求只能充值一次.
public class AtomicStampedReferenceDemo { static AtomicStampedReference<Integer> money=new AtomicStampedReference<Integer>(19,0); public staticvoid main(String[] args) { //模拟多个线程同时更新后台数据库,为用户充值 for(int i = 0 ; i < 3 ; i++) { final int timestamp=money.getStamp(); newThread() { public void run() { while(true){ while(true){ Integerm=money.getReference(); if(m<20){ if(money.compareAndSet(m,m+20,timestamp,timestamp+1)){ System.out.println("余额小于20元,充值成功,余额:"+money.getReference()+"元"); break; } }else{ //System.out.println("余额大于20元,无需充值"); break ; } } } } }.start(); } //用户消费线程,模拟消费行为 new Thread() { publicvoid run() { for(int i=0;i<100;i++){ while(true){ int timestamp=money.getStamp(); Integer m=money.getReference(); if(m>10){ System.out.println("大于10元"); if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){ System.out.println("成功消费10元,余额:"+money.getReference()); break; } }else{ System.out.println("没有足够的金额"); break; } } try {Thread.sleep(100);} catch (InterruptedException e) {} } } }.start(); } }
AtomicIntegerFieldUpdater
可以让普通变量也可以进行原子操做。
主要接口
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName); public int incrementAndGet(T obj);
-
Updater
只能修改它可见范围内的变量。由于Updater
使用反射获得这个变量。若是变量不可见,就会出错。好比若是score
申明为private
,就是不可行的。 -
为了确保变量被正确的读取,它必须是
volatile
类型的。若是咱们原有代码中未申明这个类型,那么简单得申明一下就行。 -
因为
CAS
操做会经过对象实例中的偏移量直接进行赋值,所以,它不支持static
字段(Unsafe.objectFieldOffset()
不支持静态变量)。
测试
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterDemo { public static class Candidate { int id; // 若是直接把int改为atomicinteger, 可能对代码破坏比较大 // 所以使用AtomicIntegerFieldUpdater对score进行封装 volatile int score; } // 经过反射实现 public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score"); // 检查Updater是否工做正确, allScore的结果应该跟score一致 public static AtomicInteger allScore = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { final Candidate stu = new Candidate(); Thread[] t = new Thread[10000]; for (int i = 0; i < 10000; i++) { t[i] = new Thread() { public void run() { if (Math.random() > 0.4) { scoreUpdater.incrementAndGet(stu); allScore.incrementAndGet(); } } }; t[i].start(); } for (int i = 0; i < 10000; i++) { t[i].join(); } System.out.println("score=" + stu.score); System.out.println("allScore=" + allScore); } }
JDK8以后引入的类型
在JDK8
以前,针对原子操做,咱们基本上能够经过上面提供的这些类来完成咱们的多线程下的原子操做,不过在并发高的状况下,上面这些单一的 CAS
+ 自旋操做的性能将会是一个问题,因此上述这些类通常用于低并发操做。 而针对这个问题,JDK8
又引入了下面几个类:DoubleAdder
,LongAdder
,DoubleAccumulator
,LongAccumulator
,这些类是对AtomicLong
这些类的改进与加强,这些类都继承自Striped64
这个类。
参考博客: Java高并发之无锁与Atomic源码分析