Atomic原子类
概述
原子类就是利用CAS机制,实现原子操作的一些类
(1). 基本类型原子类(AtomicInteger、AtomicBoolean、AtomicLong)
(2). 数组类型原子类 (AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray)
(3). 引用类型原子类 (AtomicReference、AtomicStampedReference、AtomicMarkableReference)
(4). 对象的属性修改原子类 (AtomicIntegerFieldUp dater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater)
(5). 原子操作增强类(DoubleAccumulator 、DoubleAdder 、LongAccumulator 、LongAdder)
原子int、long、boolean
常用方法
方法 | 解释 |
---|---|
public final int get() | 获取当前的值 |
public final int getAndSet(int newValue) | 获取到当前的值,并设置新的值 |
public final int getAndIncrement() | 获取当前的值,并自增 |
public final int getAndDecrement() | 获取到当前的值,并自减 |
public final int getAndAdd(int delta) | 获取到当前的值,并加上预期的值 |
public final int incrementAndGet( ) | 返回的是加1后的值 |
boolean compareAndSet(int expect,int update) | 如果输入的数值等于预期值,返回true |
使用
public class AtomicIntegerDemo {
AtomicInteger atomicInteger=new AtomicInteger(0);
public void addPlusPlus(){
atomicInteger.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(10);
AtomicIntegerDemo atomic=new AtomicIntegerDemo();
// 10个线程进行循环100次调用addPlusPlus的操作,最终结果是10*100=1000
for (int i = 1; i <= 10; i++) {
new Thread(()->{
try{
for (int j = 1; j <= 100; j++) {
atomic.addPlusPlus();
}
}finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t"+"获取到的result:"+atomic.atomicInteger.get());
}
}
原子数组
数组类型原子类,主要有三个AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
public class AtomicIntegerArrayDemo {
public static void main(String[] args) {
//(1). 创建一个新的AtomicIntegerArray,其长度与从给定数组复制的所有元素相同。
int[]arr2={1,2,3,4,5};
AtomicIntegerArray array=new AtomicIntegerArray(arr2);
//(2). 创建给定长度的新AtomicIntegerArray,所有元素最初为零。
//AtomicIntegerArray array=new AtomicIntegerArray(5);
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]);
}
System.out.println();
System.out.println("=======");
array.getAndSet(0,1111);
System.out.println("============");
System.out.println("将数字中位置为0位置上的元素改为:"+array.get(0));
System.out.println("数组位置为1位置上的旧值是:"+array.get(1));
System.out.println("将数组位置为1位置上的数字进行加1的处理");
array.getAndIncrement(1);
System.out.println("数组位置为1位置上的新值是:"+array.get(1));
}
}
原子引用类型
引用类型原子类主要有三个:
AtomicReference 和Integer等类型的原子类类似,只不过利用了泛型,可以利用CAS机制原子操作各种类型的类
AtomicStampedReference 带有版本标识的类,可以用于解决ABA问题
AtomicMarkableReference 带有是否被修改过标识的类,与AtomicStampedReference 区别在于版本号是可以1-2-3这样增加的,也就可以清除被修改了几次,而markable的只能true、false来判断是否被修改过,而不知道修改了多少次。
AtomicReference实现自旋锁
//自旋锁
public class AtomicReferenceThreadDemo {
static AtomicReference<Thread>atomicReference=new AtomicReference<>();
static Thread thread;
public static void lock(){
thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t"+"coming.....");
while(!atomicReference.compareAndSet(null,thread)){
}
}
public static void unlock(){
System.out.println(Thread.currentThread().getName()+"\t"+"over.....");
atomicReference.compareAndSet(thread,null);
}
public static void main(String[] args) {
new Thread(()->{
AtomicReferenceThreadDemo.lock();
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}
AtomicReferenceThreadDemo.unlock();
},"A").start();
new Thread(()->{
AtomicReferenceThreadDemo.lock();
AtomicReferenceThreadDemo.unlock();
},"B").start();
}
}
AtomicStampedReference 解决ABA问题
public class ABADemo {
private static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
private static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("===以下是ABA问题的产生===");
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
//先暂停1秒 保证完成ABA
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"t2").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("===以下是ABA问题的解决===");
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//暂停1秒钟t3线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第2次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第3次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
},"t3").start();
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//保证线程3完成1次ABA
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本号"+stampedReference.getStamp());
System.out.println("最新的值\t"+stampedReference.getReference());
},"t4").start();
}
对象属性修改原子类
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
看类名,很明显,一个Integer一个Long一个引用类型
以一种线程安全的方式操作非线程安全对象内的某些字段,只对这个字段进行锁定,区别于synchroized对于整个对象的锁定。
在使用时:更新的对象属性必须使用public volatile修饰符,因为对象属性修改原子类都是抽象类,所以在创建原子类时需要使用静态方法
@SuppressWarnings("all")
public class AtomicIntegerFieldUpdaterDemo {
private static final int THREAD_NUM = 1000;
private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM);
Score score=new Score();
public static void main(String[] args)throws InterruptedException {
Score score = new Score();
for (int j = 0; j < THREAD_NUM; j++) {
new Thread(() -> {
score.addTotalScore(score);
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println("totalScore的值:" + score.totalScore);
}
}
class Score {
String username;
public volatile int totalScore = 0;
private static AtomicIntegerFieldUpdater atomicIntegerFieldUpdater =
AtomicIntegerFieldUpdater.newUpdater(Score.class, "totalScore");
public void addTotalScore(Score score){
// 这里需要调用updater的方法,此方法会使newUpdater方法传入的字段名对应的字段进行 ++ ;
atomicIntegerFieldUpdater.incrementAndGet(score);
}
}
对比
public class AtomicFieldUpdaterDemo {
public static class Candidate {
int id;
volatile int score = 0;
AtomicInteger score2 = new AtomicInteger();
}
public static final AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
public static AtomicInteger realScore = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate candidate = new Candidate();
Thread[] t = new Thread[10000];
for (int i = 0; i < 10000; i++) {
t[i] = new Thread() {
@Override
public void run() {
if (Math.random() > 0.4) {
candidate.score2.incrementAndGet();
scoreUpdater.incrementAndGet(candidate);
realScore.incrementAndGet();
}
}
};
t[i].start();
}
for (int i = 0; i < 10000; i++) {
t[i].join();
}
System.out.println("AtomicIntegerFieldUpdater Score=" + candidate.score);
System.out.println("AtomicInteger Score=" + candidate.score2.get());
System.out.println("realScore=" + realScore.get());
}
}
上方的代码执行下来AtomicInteger与AtomicIntegerFieldUpdater结果是相同的,那既然已经存在了AtomicInteger为什么有AtomicIntegerFieldUpdater呢?
- AtomicIntegerFieldUpdater可以作为扩展,不侵入原对象的属性中,可以在需要原子操作的对象类之外定义,而使用AtomicInteger则需要修改类中属性类型
- 而且AtomicIntegerFieldUpdater可以是static的,而AtomicInteger是每个对象都需要创建一个的,内存占用方面也更加少
dubbo源码中有个实现轮询负载均衡策略的类AtomicPositiveInteger使用的就是AtomicIntegerFieldUpdater。
LongAdder、LongAccumulator
概述
上面说的那些原子类都since jdk1.5,而这个类since 1.8
LongAdder相比于AtomicLong来说,性能更好,减少了CAS锁的重试次数
- LongAdder只能用来计算加法、减法,且从零开始计算
- LongAccumulator提供了自定义的函数操作
LongAccumulator相比于LongAdder,可以自定义函数操作,比如进行乘法、除法等
使用
方法 | 解释 |
---|---|
void add(long x) | 将当前的value加 x |
void increment( ) | 将当前的value加1 |
void decrement( ) | 将当前value减1 |
long sum( ) | 返回当前的值,特别注意,在没有并发更新value的情况下,sum会返回一个精确值,在存在并发的情况下,sum不保证返回精确值 |
long longvale | 等价于long sum( ),将value重置为0,可用于替换重新new一个LongAdder,但次方法只可以在没有并发更新的情况下使用 |
long sumThenReset() | 获取当前value,并将value重置为0 |
public class LongAdderDemo {
public static void main(String[] args) {
// LongAdder只能做加减法,不能做乘除法
LongAdder longAdder=new LongAdder();
longAdder.increment();
longAdder.increment();
longAdder.increment();
longAdder.decrement();
System.out.println(longAdder.longValue());
System.out.println("========");
//LongAccumulator(LongBinaryOperator accumulatorFunction, long identity)
//LongAccumulator longAccumulator=new LongAccumulator((x,y)->x+y,0);
LongAccumulator longAccumulator=new LongAccumulator(new LongBinaryOperator() {
@Override
public long applyAsLong(long left, long right) {
return left*right;
}
},5);
longAccumulator.accumulate(1);
longAccumulator.accumulate(2);
longAccumulator.accumulate(3);
System.out.println(longAccumulator.longValue());
}
}
原理
LongAdder为什么比AtomicLong快呢,这就需要究其原理了
LongAdder继承自Striped64类。在Striped64中有一个参数Cell[]数组
,Cell
是一个内部类。有一个属性long base
就是用来保存值的。
在并发量少的情况下,跟AtomicLong差不多,但是在高并发的情况下,就会开启Cell数组,分散线程请求。
在高并发的情况下,会创建一个cell数组,每个cell数组分别有一个value值,不同的线程会取hash值操作不同的cell 的 value值。初始情况下,cell数组初始化大小为2,之后每次乘2(2的幂)进行扩容2、4、8、16、32。
在获取值得时候,则是将base值加上所有得cell中的value值。
源码
LongAdder主要方法就是add方法,add方法中,如果cell没被初始化,则会在base上加,如果cell已经初始化了或者base加失败了则进入longAccumulate方法,方法中是一个循环。
如果当前线程对应的cell还没被使用,则new一个使用。
如果已经被使用了,则尝试在上面加,如果成功则返回,不成功则重新计算hash重新进入循环。
重新在此cell上加,如果cas还是失败,则会检查是否 其他线程已经进行扩容 或者 cell数组长度大于等于CPU的线程数,如果都没有,则去扩容,否则会重新hash进行重新循环。(扩容上限为CPU线程数)
处理如果cell数组没有初始化,并且其它线程正在执行对cells数组初始化的操作
// 累加方法,参数x为累加的值
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
/**
* 如果一下两种条件则继续执行if内的语句
* 1. cells数组不为null(不存在争用的时候,cells数组一定为null,一旦对base的cas操作失败,才会初始化cells数组)
* 2. 如果cells数组为null,如果casBase执行成功,则直接返回,如果casBase方法执行失败(casBase失败,说明第一次争用冲突产生,需要对cells数组初始化)进入if内;
* casBase方法很简单,就是通过UNSAFE类的cas设置成员变量base的值为base+要累加的值
* casBase执行成功的前提是无竞争,这时候cells数组还没有用到为null,可见在无竞争的情况下是类似于AtomticInteger处理方式,使用cas做累加。
*/
if ((as = cells) != null || !casBase(b = base, b + x)) {
//uncontended判断cells数组中,当前线程要做cas累加操作的某个元素是否#不#存在争用,如果cas失败则存在争用;uncontended=false代表存在争用,uncontended=true代表不存在争用。
boolean uncontended = true;
/**
*1. as == null : cells数组未被初始化,成立则直接进入if执行cell初始化
*2. (m = as.length - 1) < 0: cells数组的长度为0
*条件1与2都代表cells数组没有被初始化成功,初始化成功的cells数组长度为2;
*3. (a = as[getProbe() & m]) == null :如果cells被初始化,且它的长度不为0,则通过getProbe方法获取当前线程Thread的threadLocalRandomProbe变量的值,初始为0,然后执行threadLocalRandomProbe&(cells.length-1 ),相当于m%cells.length;如果cells[threadLocalRandomProbe%cells.length]的位置为null,这说明这个位置从来没有线程做过累加,需要进入if继续执行,在这个位置创建一个新的Cell对象;
*4. !(uncontended = a.cas(v = a.value, v + x)):尝试对cells[threadLocalRandomProbe%cells.length]位置的Cell对象中的value值做累加操作,并返回操作结果,如果失败了则进入if,重新计算一个threadLocalRandomProbe;
如果进入if语句执行longAccumulate方法,有三种情况
1. 前两个条件代表cells没有初始化,
2. 第三个条件指当前线程hash到的cells数组中的位置还没有其它线程做过累加操作,
3. 第四个条件代表产生了冲突,uncontended=false
**/
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
//获取当前线程的threadLocalRandomProbe值作为hash值,如果当前线程的threadLocalRandomProbe为0,说明当前线程是第一次进入该方法,则强制设置线程的threadLocalRandomProbe为ThreadLocalRandom类的成员静态私有变量probeGenerator的值,后面会详细将hash值的生成;
//另外需要注意,如果threadLocalRandomProbe=0,代表新的线程开始参与cell争用的情况
//1.当前线程之前还没有参与过cells争用(也许cells数组还没初始化,进到当前方法来就是为了初始化cells数组后争用的),是第一次执行base的cas累加操作失败;
//2.或者是在执行add方法时,对cells某个位置的Cell的cas操作第一次失败,则将wasUncontended设置为false,那么这里会将其重新置为true;第一次执行操作失败;
//凡是参与了cell争用操作的线程threadLocalRandomProbe都不为0;
int h;
if ((h = getProbe()) == 0) {
//初始化ThreadLocalRandom;
ThreadLocalRandom.current(); // force initialization
//将h设置为0x9e3779b9
h = getProbe();
//设置未竞争标记为true
wasUncontended = true;
}
//cas冲突标志,表示当前线程hash到的Cells数组的位置,做cas累加操作时与其它线程发生了冲突,cas失败;collide=true代表有冲突,collide=false代表无冲突
boolean collide = false;
for (;;) {
Cell[] as; Cell a; int n; long v;
//这个主干if有三个分支
//1.主分支一:处理cells数组已经正常初始化了的情况(这个if分支处理add方法的四个条件中的3和4)
//2.主分支二:处理cells数组没有初始化或者长度为0的情况;(这个分支处理add方法的四个条件中的1和2)
//3.主分支三:处理如果cell数组没有初始化,并且其它线程正在执行对cells数组初始化的操作,及cellbusy=1;则尝试将累加值通过cas累加到base上
//先看主分支一
if ((as = cells) != null && (n = as.length) > 0) {
/**
*内部小分支一:这个是处理add方法内部if分支的条件3:如果被hash到的位置为null,说明没有线程在这个位置设置过值,没有竞争,可以直接使用,则用x值作为初始值创建一个新的Cell对象,对cells数组使用cellsBusy加锁,然后将这个Cell对象放到cells[m%cells.length]位置上
*/
if ((a = as[(n - 1) & h]) == null) {
//cellsBusy == 0 代表当前没有线程cells数组做修改
if (cellsBusy == 0) {
//将要累加的x值作为初始值创建一个新的Cell对象,
Cell r = new Cell(x);
//如果cellsBusy=0无锁,则通过cas将cellsBusy设置为1加锁
if (cellsBusy == 0 && casCellsBusy()) {
//标记Cell是否创建成功并放入到cells数组被hash的位置上
boolean created = false;
try {
Cell[] rs; int m, j;
//再次检查cells数组不为null,且长度不为空,且hash到的位置的Cell为null
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
//将新的cell设置到该位置
rs[j] = r;
created = true;
}
} finally {
//去掉锁
cellsBusy = 0;
}
//生成成功,跳出循环
if (created)
break;
//如果created为false,说明上面指定的cells数组的位置cells[m%cells.length]已经有其它线程设置了cell了,继续执行循环。
continue;
}
}
//如果执行的当前行,代表cellsBusy=1,有线程正在更改cells数组,代表产生了冲突,将collide设置为false
collide = false;
/**
*内部小分支二:如果add方法中条件4的通过cas设置cells[m%cells.length]位置的Cell对象中的value值设置为v+x失败,说明已经发生竞争,将wasUncontended设置为true,跳出内部的if判断,最后重新计算一个新的probe,然后重新执行循环;
*/
} else if (!wasUncontended)
//设置未竞争标志位true,继续执行,后面会算一个新的probe值,然后重新执行循环。
wasUncontended = true;
/**
*内部小分支三:新的争用线程参与争用的情况:处理刚进入当前方法时threadLocalRandomProbe=0的情况,也就是当前线程第一次参与cell争用的cas失败,这里会尝试将x值加到cells[m%cells.length]的value ,如果成功直接退出
*/
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
/**
*内部小分支四:分支3处理新的线程争用执行失败了,这时如果cells数组的长度已经到了最大值(大于等于cup数量),或者是当前cells已经做了扩容,则将collide设置为false,后面重新计算prob的值*/
else if (n >= NCPU || cells != as)
collide = false;
/**
*内部小分支五:如果发生了冲突collide=false,则设置其为true;会在最后重新计算hash值后,进入下一次for循环
*/
else if (!collide)
//设置冲突标志,表示发生了冲突,需要再次生成hash,重试。 如果下次重试任然走到了改分支此时collide=true,!collide条件不成立,则走后一个分支
collide = true;
/**
*内部小分支六:扩容cells数组,新参与cell争用的线程两次均失败,且符合库容条件,会执行该分支
*/
else if (cellsBusy == 0 && casCellsBusy()) {
try {
//检查cells是否已经被扩容
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
//为当前线程重新计算hash值
h = advanceProbe(h);
//这个大的分支处理add方法中的条件1与条件2成立的情况,如果cell表还未初始化或者长度为0,先尝试获取cellsBusy锁。
}else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
//初始化cells数组,初始容量为2,并将x值通过hash&1,放到0个或第1个位置上
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
//解锁
cellsBusy = 0;
}
//如果init为true说明初始化成功,跳出循环
if (init)
break;
}
/**
*如果以上操作都失败了,则尝试将值累加到base上;
*/
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) // Fall back on using base
break;
}
}