原子操作类

目录

1.有哪些 

2.分类

        2.1基本类型原子类

        2.2数组类型原子类

        2.3引用类型原子类

        2.4对象的属性修改原子类

        2.5原子操作增强类原理深度解析


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就打高了。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值