目录
1.有哪些
是java.util.concurrent.atomic包下的原子类。
2.分类
2.1基本类型原子类
分为
AtomicInteger、AtomicBoolean、AtomicLong
原子的更新int、boolean、long。
常用API
public final int get() //获取当前的值
public final int getAndSet(int newValue) //获取当前的值,并设置新的值
public final int getAndIncrement() //获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上了预期的值
boolean compareAndSet(int expect,int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
案例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
class MyNumber{
AtomicInteger atomicInteger=new AtomicInteger();
public void addPlusPlus(){
atomicInteger.getAndIncrement();
}
}
public class AtomicIntegerDemo {
public static final int SIZE=50;
public static void main(String[] args) throws InterruptedException {
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for (int i = 0; i < SIZE; i++) {
new Thread(()->{
try {
for (int j = 0; j < 1000; j++) {
myNumber.addPlusPlus();
}
}finally {
countDownLatch.countDown();//计算完成一个减1
}
},String.valueOf(i)).start();
}
//等上面50个线程全部计算完成后,在去获得最终值
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\tresult: "+myNumber.atomicInteger.get());
}
}
2.2数组类型原子类
分为
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
一个int、long、引用类型的数组,其中元素可以原子更新。
案例
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayDemo {
public static void main(String[] args) {
// AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
// AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
for (int i = 0; i < atomicIntegerArray.length(); i++) {
System.out.println(atomicIntegerArray.get(i));
}
int temp=0;
temp=atomicIntegerArray.getAndSet(0,2022);
System.out.println(temp+"\t"+atomicIntegerArray.get(0));
temp=atomicIntegerArray.getAndIncrement(0);
System.out.println(temp+"\t"+atomicIntegerArray.get(0));
}
}
2.3引用类型原子类
分为
AtomicReference:可以原子更新的对象引用。
AtomicStampedReference:维护对象引用以及可以原子更新的整型标志。
AtomicMarkableReference:维护一个对象引用以及原子更新的标志位(true、false)。
案例
AtomicReference案例
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/*
题日:实现一个自旋锁,复习CAS思想
自旋锁好处:循环比较获取没有类他wait的阻塞.
通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁3秒钟,B随后进来后发现
当前有线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。
*/
public class SpinLockDemo {
AtomicReference<Thread> atomicReference=new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void unLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t"+"----task over,unLock");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.lock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.unLock();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.lock();
spinLockDemo.unLock();
},"B").start();
}
}
AtomicStampedReference案例( 携带版本号的引用类型原子类, 可以解决ABA问题,可以知道修改过几次)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 原子类里面的数据被修改过了,又被改回来,但只能知道结果,不知道过程,也就是ABA问题
* 用AtomicStampedReference类解决,通过版本号控制
*/
public class ABADemo {
static AtomicInteger atomicInteger=new AtomicInteger(100);
static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t首次版本号:"+stamp);
//保证后面的t4线程初始化拿到的版本号和我一致
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t2次版本号:"+stampedReference.getStamp());
stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t3次版本号:"+stampedReference.getStamp());
},"t3").start();
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t首次版本号:"+stamp);
//等待上面的t3线程,发生ABA问题
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t"+b+"\t"+stampedReference.getReference());
System.out.println(Thread.currentThread().getName()+"\t版本号:"+stampedReference.getStamp());
},"t4").start();
}
public static void aba() {
new Thread(()->{
atomicInteger.compareAndSet(100,101);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(100,2022)+"\t"+atomicInteger.get());
},"t2").start();
}
}
AtomicMarkableReference案例(原子更新带有标记位的引用类型对象,它的定义就是将状态截简化为true l false,解决是否修改过的问题,类似一次性筷子)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableReferenceDemo {
static AtomicMarkableReference markableReference=new AtomicMarkableReference(100,false);
public static void main(String[] args) {
new Thread(()->{
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName()+"\t默认标识:"+marked);//t1 默认标识:false
//等待后面的T2线程和我拿到一样的flag标识,都是false
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
markableReference.compareAndSet(100,1000,marked,!marked);
},"t1").start();
new Thread(()->{
boolean marked = markableReference.isMarked();
System.out.println(Thread.currentThread().getName()+"\t默认标识:"+marked);//t2 默认标识:false
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean b = markableReference.compareAndSet(100, 2022, marked, !marked);
System.out.println(Thread.currentThread().getName()+"\tresult:"+b);//t2 result:false
System.out.println(Thread.currentThread().getName()+"\t"+markableReference.isMarked());//t2 true
System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference());//t2 1000
},"t2").start();
}
}
2.4对象的属性修改原子类
分为
AtomicIntegerFieldUpdater:基于反射的实用程序, 可对指定类的指定 volatile int字段进行原子更新。
AtomicLongFieldUpdater:基于反射的实用程序, 可对指定类的指定 volatile long字段进行原子更新。
AtomicReferenceFieldUpdate:基于反射的实用程序,可对指定类的指定 volatile 引用字段进行原子更新。
使用目的
以一种线程安全的方式操作非线程安全对象内的某些字段。
使用要求
1)更新的对象属性必须使用publicvolatile修饰符。
2)因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdaterO创建一个更新器, 并且需要设置想要更新的类和属性。
面试官问哪里用到了volatile:AtomicReferenceFieldUpdater
案例
AtomicIntegerFieldUpdater案例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class BankCount{//资源类
String bankName="ZSYH";
//更新的对象属性必须使用 public volatile 修饰
public volatile int money=0;
//效率低
public synchronized void add(){
money++;
}
//因为对象的属性修改类型原子类都是抽象类, 所以每次使用都必须
//使用静态方法newUpdater()创建一个更新器, 并且需要设置想要更新的类和属性:
AtomicIntegerFieldUpdater<BankCount> fieldUpdater=
AtomicIntegerFieldUpdater.newUpdater(BankCount.class,"money");
//不加synchronized,保证高性能原子性,相当于局部麻醉处理
public void transMoney(BankCount bankCount){
fieldUpdater.getAndIncrement(bankCount);
}
}
/*
以一种线程安全的方式操作非线程安全对象的某些字段。
需求:
10个线程,
每个线程转账1000,
不使用synchronized,尝试使用AtomicIntegerFieldUpdater来实现。
*/
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) throws InterruptedException {
BankCount bankCount = new BankCount();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
for (int j = 0; j < 1000; j++) {
// bankCount.add();
bankCount.transMoney(bankCount);
}
}finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\tresult:"+bankCount.money);
}
}
AtomicReferenceFieldUpdater案例
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/*
需求:
多线程并发调用一个类的初始化方法,如果未被初始化,将执行初始化工作,
要求只能初始化一次,只有一个线程操作成功
*/
class MyVar{
public volatile Boolean isInit=Boolean.FALSE;
AtomicReferenceFieldUpdater referenceFieldUpdater=
AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");
public void init(MyVar myVar) throws InterruptedException {
if (referenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE)){
System.out.println(Thread.currentThread().getName()+"\t---- start init,need 2 seconds");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"\t---- over init");
}else {
System.out.println(Thread.currentThread().getName()+"\t---- 已有线程在进行初始化工作...");
}
}
}
public class AtomicReferenceFieldUpdaterDemo {
public static void main(String[] args) {
MyVar myVar = new MyVar();
for (int i = 0; i < 5; i++) {
new Thread(()->{
try {
myVar.init(myVar);
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
2.5原子操作增强类原理深度解析
分为
DoubleAccumulator:一个或多个变量一起维护使用提供的功能更新的运行的值 double 。
DoubleAdder:一个或多个变量一起保持初始为零 double和。
LongAccumulator:一个或多个变量,它们一起保持运行 long使用所提供的功能更新值。
LongAdder:一个或多个变量一起保持初始为零 long总和。
阿里面试题
LongAdder说明
一个或多个变量一起维持初始为零long总和。 当更新(方法add(long) )跨线程竞争时,变量集可以动态增长以减少争用。 方法sum() (或等效地, longValue() )返回保持总和的整个变量组合的当前总和。
这个类是通常优选AtomicLong当多个线程更新时使用,用于诸如收集统计信息,不用于细粒度同步控制的共同总和。 在低更新争议下,这两类具有相似的特征。 但是,在高度争议的情况下,这一类的预期吞吐量明显高于牺牲更高的空间消耗。
LongAdders可以使用ConcurrentHashMap来维护可扩展的频率映射(一种直方图或多集)。 例如,要向ConcurrentHashMap<String,LongAdder> freqs添加一个计数,如果尚未存在,则可以使用freqs.computeIfAbsent(k -> new LongAdder()).increment();
该类扩展Number ,但不定义诸如方法equals , hashCode和compareTo ,因为实例预计将发生突变,所以不如收集钥匙有用。
常用API
入门讲解
LongAdder只能用来计算加法, 且从零开始计算
LongAccumulator提供了自定义的函数操作
代码演示
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
public class LongAdderAPIDemo {
public static void main(String[] args) {
LongAdder longAdder = new LongAdder();
longAdder.increment();
longAdder.increment();
longAdder.increment();
//sum()会将所有Cell数组中的value和base累加作为返回值。
//核心的思想就是将之前AtomicLong 一个value的更新压力分散到多个value中去,从而降级更新热点。
System.out.println(longAdder.sum());//3
LongAccumulator longAccumulator = new LongAccumulator((x,y)->x+y,0);
longAccumulator.accumulate(1);//1
longAccumulator.accumulate(3);//4
System.out.println(longAccumulator.get());//4
}
}
Long Adder高性能对比代码演示
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
class ClickNumber{//资源类
int number=0;
//方式1
public synchronized void clickBySynchronized(){
number++;
}
//方式2
AtomicLong atomicLong=new AtomicLong(0);
public void clickByAtomicLong(){
atomicLong.getAndIncrement();
}
//方式3
LongAdder longAddr=new LongAdder();
public void clickByLongAdder(){
longAddr.increment();
}
//方式4
LongAccumulator longAccumulator=new LongAccumulator((x,y)->x+y,0);
public void clickByLongAccumulator(){
longAccumulator.accumulate(1);
}
}
/**
* 需求,50个线程,每个线程100w次,总点赞数
*/
public class AccumulatorCompareDemo {
public static final int _1W=10000;
public static final int threadNumber=50;
public static void main(String[] args) throws InterruptedException {
ClickNumber clickNumber = new ClickNumber();
long startTime,endTime;
CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);
startTime=System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(()->{
try {
for (int j = 1; j <=100*_1W; j++) {
clickNumber.clickBySynchronized();
}
}finally {
countDownLatch1.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch1.await();
endTime=System.currentTimeMillis();
System.out.println("----costTime: "+(endTime-startTime)+" 毫秒\t clickBySynchronized: "+clickNumber.number);
startTime=System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(()->{
try {
for (int j = 1; j <=100*_1W; j++) {
clickNumber.clickByAtomicLong();
}
}finally {
countDownLatch2.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch2.await();
endTime=System.currentTimeMillis();
System.out.println("----costTime: "+(endTime-startTime)+" 毫秒\t clickByAtomicLong: "+clickNumber.atomicLong.get());
startTime=System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(()->{
try {
for (int j = 1; j <=100*_1W; j++) {
clickNumber.clickByLongAdder();
}
}finally {
countDownLatch3.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch3.await();
endTime=System.currentTimeMillis();
System.out.println("----costTime: "+(endTime-startTime)+" 毫秒\t clickByLongAdder: "+clickNumber.longAddr.longValue());
startTime=System.currentTimeMillis();
for (int i = 1; i <=threadNumber; i++) {
new Thread(()->{
try {
for (int j = 1; j <=100*_1W; j++) {
clickNumber.clickByLongAccumulator();
}
}finally {
countDownLatch4.countDown();
}
},String.valueOf(i)).start();
}
countDownLatch4.await();
endTime=System.currentTimeMillis();
System.out.println("----costTime: "+(endTime-startTime)+" 毫秒\t clickByLongAccumulator: "+clickNumber.longAccumulator.get());
}
}
运行结果
源码、原理分析(LongAdder为什么这么快)
LongAdder是String64的子类
String64有几个比较重要的成员函数
String64中一些变量或者方法的定义
过程
LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。
sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
内部有一个base变量,一个Cell数组。
base变量:低并发,直接累加到该变量上。
Cell数组:高并发,累加到各个线程自己的槽Cell中。
说明
LongAdder在无竞争的情况,跟AtomicLong一样,对同 一个base进行操作,当出现竞争关系时则是采用化整为零分散热点的做法,用空间换时间,用一个数组cells,将一个value拆分进这个数组cells。多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值,再根据
hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组cells的所有值和base都加起来作为最终结果。
LongAdder中的sum方法为啥在并发情况下sum的值不准确
sum()会将所有Cell数组中的value和base累加作为返回值。
核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
首先, 最终返回的sum局部变量, 初始被复制为base, 而最终返回时, 很可能base已经被更新了, 而此时局部变量sum不会更新,造成不一致。
其次, 这里对cell的读取也无法保证是最后一次写入的值。所以, sum方法在没有并发的情况下, 可以获得正确的结果。
使用总结
AtomicLong
1)线程安全,可允许一些性能损耗,要求高精度时可使用
2)保证精度,性能代价
3)AtomicLong是多个线程针对单个热点值value进行原子操作
LongAdder
1) 当需要在高并发下有较好的性能表现, 且对值的精确度要求不高时,可以使用
2)保证性能,精度代价
3)LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作
总结
Atomiclong的自旋会成为瓶颈:
N个线程CAS操作修改线程的值,每次只有一个成功过,其它N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。