首页 > 编程语言 >Java并发JUC——Atomic原子类

Java并发JUC——Atomic原子类

时间:2023-01-31 17:04:16浏览次数:52  
标签:JUC LongAdder Java Thread int 原子 Atomic new public

什么是原子类

  • 原子是不可分割的最小单位,故原子类可以认为其操作都是不可分割
  • 一个操作时不可中断的,即便是在多线程的情况下也可以保证
  • 原子类的作用和锁类似,是为了保证并发情况下线程安全,不过原子类相比于锁,有一定的优势
  • 粒度更细:原子变量可以把竞争范围缩小到变量级别,这是我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度
  • 效率更高:通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况

6类原子类纵览

Atomic基本原子类型

AtomicBoolean布尔型原子类、AtomicInteger整型原子类、AutomicLong长整型原子类,元老级的原子更新,方法几乎一模一样

以AtomicInteger为例

AtomicInteger常用方法

  • 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 boolean compareAndSet(int expect, int update):如果输入的值等于预期的值,则以原子方式将该值设置为输入的值(update)
/**
 * @Description: 演示AtomicInteger的基本用法,对比非原子类的线程安全问题,
 *                使用了原子类之后不需要加锁,也可以保证线程安全
 */
public class AtomicIntegerDemo1 implements Runnable {

    public static final AtomicInteger atomicInteger = new AtomicInteger();

    public void incrementAtomic(){
        atomicInteger.getAndIncrement();
    }

    public static volatile int basicCount = 0;

    /**
     * 普通变量必须要加锁才能保证线程安全问题
     */
    public synchronized void incrementBasic(){
        basicCount++;
    }
    
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            incrementAtomic();
            incrementBasic();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerDemo1 atomicIntegerDemo1 = new AtomicIntegerDemo1();
        Thread thread1 = new Thread(atomicIntegerDemo1);
        Thread thread2 = new Thread(atomicIntegerDemo1);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("原子类的结果:"+atomicInteger.get());
        System.out.println("普通变量的结果:"+basicCount);
    }
}

原子更新数组

通过原子的方式更新数组里的某个元素,Atomic包提供了以下三个类:

  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。

AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,其常用方法如下

  • int addAndGet(int i, int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

AtomicIntegerArray的使用

/**
 * @Description: 演示原子数组的使用方法
 */
public class AtomicArrayDemo1 {

    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
        Thread[] threads1 = new Thread[100];
        Thread[] threads2 = new Thread[100];
        Decrement decrement = new Decrement(atomicIntegerArray);
        Increment increment = new Increment(atomicIntegerArray);
        for (int i = 0; i < 100; i++) {
            threads1[i] = new Thread(decrement);
            threads2[i] = new Thread(increment);

            threads1[i].start();
            threads2[i].start();
        }

        for (int i = 0; i < 100; i++) {
            threads1[i].join();
            threads2[i].join();
        }

        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            if(atomicIntegerArray.get(i) != 0){
                System.out.println("发现了错误:错误的索引:"+i);
            }
        }
        System.out.println("运行结束");
    }

}

class Decrement implements Runnable{

    private AtomicIntegerArray array;

    public Decrement(AtomicIntegerArray array) {
        this.array = array;
    }

    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndDecrement(i);
        }
    }
}

class Increment implements Runnable{

    private AtomicIntegerArray array;

    public Increment(AtomicIntegerArray array) {
        this.array = array;
    }

    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndIncrement(i);
        }
    }
}

AtomicIntegerArray类需要注意的是,数组value通过构造方法传递进去,然后AtomicIntegerArray会将当前数组复制一份,所以当AtomicIntegerArray对内部的数组元素进行修改时,不会影响到传入的数组。

原子更新引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子的更新多个变量,就需要使用这个原子更新引用类型提供的类。Atomic包提供了以下三个类:

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  • AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

AtomicReference引用类型原子类

  • AtomicReference:AtomicReference类的作用,和AtomicInteger并没有本质区别,AtomicInteger可以让一个整数保证原子性,AtomicReference可以让一个对象保证原子性,当然,AtomicReference的功能明显比AtomicInteger强,因为一个对象里可以包含很多属性,其用法和AtomicInteger类似

AtomicReference的使用

public class SpinLock {

    private AtomicReference<Thread> sign = new AtomicReference<>();

    public void lock(){
        Thread current = Thread.currentThread();
        while(!sign.compareAndSet(null,current)){
            System.out.println(Thread.currentThread().getName() + "自旋锁获取失败,再次尝试");
        }
    }

    public void unlock(){
        Thread current = Thread.currentThread();
        sign.compareAndSet(current,null);
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "尝试获取自旋锁");
                spinLock.lock();
                System.out.println(Thread.currentThread().getName() + "获取到了自旋锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    spinLock.unlock();
                    System.out.println(Thread.currentThread().getName() + "释放了自旋锁");
                }
            }
        };

        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}

原子更新字段类

如果我们只需要更新某个类里的某个字段,那么就需要使用原子更新字段类,Atomic包提供了以下三个类:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。
  • 原子更新字段类都是抽象类,每次使用都时候必须使用静态方法newUpdater创建一个更新器。原子更新类的字段的必须使用public volatile修饰符。

使用上述类是必须遵循以下原则:

  • 字段必须是voliatile类型的,在线程之间共享变量时能保持可见性。
  • 字段的描述类型是与调用者的操作对象字段保持一致。
  • 也就是说调用者可以直接操作对象字段,那么就可以反射进行原子操作。
  • 对父类的字段,子类不能直接操作的,尽管子类可以访问父类的字段
  • 只能是实例变量,不能是类变量,也就是说不能加static关键字
  • 只能是可修改变量,不能使用final修饰变量,final的语义,不可更改。

AtomicIntegerFieldUpdater

  • AtomicIntegerFieldUpdater:对普通变量进行升级,让其拥有原子操作能力
  • 使用场景:偶尔需要一个原子get-set操作

AtomicIntegerFieldUpdater注意点

  • 可以修改的变量具有可见性,即volatile修饰的变量
  • 该变量不可以被static修饰

AtomicIntegerFieldUpdater使用

/**
 * @Description: 演示AtomicIntegerFieldUpdater用法
 */
public class AtomicIntegerFieldUpdaterDemo implements Runnable {

    static Candidate tom;
    static Candidate peter;

    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
            AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            peter.score++;
            scoreUpdater.getAndIncrement(tom);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        tom = new Candidate();
        peter = new Candidate();
        AtomicIntegerFieldUpdaterDemo updaterDemo = new AtomicIntegerFieldUpdaterDemo();
        Thread thread1 = new Thread(updaterDemo);
        Thread thread2 = new Thread(updaterDemo);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("普通变量自增:"+peter.score);
        System.out.println("AtomicIntegerFieldUpdater操作的变量:"+tom.score);
    }

    public static class Candidate{
        volatile int score;
    }
}

高性能原子类

高性能原子类,是java8中增加的原子类,它们使用分段的思想,把不同的线程hash到不同的段上去更新,最后再把这些段的值相加得到最终的值,这些类主要有:

  • 1、Striped64:下面四个类的父类。
  • 2、LongAccumulator:long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等。
  • 3、LongAdder:long类型的累加器,LongAccumulator的特例,只能用来计算加法,且从0开始计算。
  • 4、DoubleAccumulator:double类型的聚合器,需要传入一个double类型的二元操作,可以用来计算各种聚合操作,包括加乘等。
  • 5、DoubleAdder:double类型的累加器,DoubleAccumulator的特例,只能用来计算加法,且从0开始计算。
  • 这几个类的操作基本类似,其中DoubleAccumulator和DoubleAdder底层其实也是用long来实现的

Adder累加器

  • 高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间
  • 在竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性
  • AtomicLong中有个内部变量value保存着实际的long值,所有的操作都是针对该变量进行。也就是说,高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。由于竞争很激烈,每一次操作,都要flush和refresh,导致很多资源浪费
  • LongAdder的基本思路就是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

演示高并发场景下LongAdder比AtomicLong性能好

/**
 * @Description: 演示高并发场景下LongAdder比AtomicLong性能好
 */
public class AtomicLongDemo {

    public static void main(String[] args) throws InterruptedException {
        AtomicLong counter = new AtomicLong(0);

        ExecutorService executorService = Executors.newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            executorService.submit(new Task(counter));
        }
        executorService.shutdown();
        while(!executorService.isTerminated()){

        }
        long end = System.currentTimeMillis();
        System.out.println(counter.get());
        System.out.println("AtomicLong耗时:"+(end - start));
    }


    static class Task implements Runnable{

        private AtomicLong counter;

        public Task(AtomicLong counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.incrementAndGet();
            }
        }
    }
}

/**
 * @Description: 演示高并发场景下LongAdder比AtomicLong性能好
 */
public class LongAdderDemo {

    public static void main(String[] args) throws InterruptedException {
        LongAdder counter = new LongAdder();

        ExecutorService executorService = Executors.newFixedThreadPool(20);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            executorService.submit(new Task(counter));
        }
        executorService.shutdown();
        while(!executorService.isTerminated()){

        }
        long end = System.currentTimeMillis();
        System.out.println(counter.sum());
        System.out.println("LongAdder耗时:"+(end - start));
    }

    static class Task implements Runnable{

        private LongAdder counter;

        public Task(LongAdder counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        }
    }
}

LongAdder比AtomicLong对比

  • 在低征用下,AtomicLong和LongAdder这两个类具有相似的特征,但在竞争激烈的情况下,LongAdder的预期吞吐量要高得多,但要消耗更多的空间
  • LongAdder适合的场景是统计求和计数的场景,而且LongAdder基本只提供了add等方法,而AtomicLong还具有CAS方法

Accumulator累加器

  • Accumulator和Adder非常相似,Accumulator就是一个更通用版本的Adder

LongAccumulator的用法

/**
 * @Description: 演示LongAccumulator的用法
 *
 */
public class LongAccumulatorDemo {

    public static void main(String[] args) {
        LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0);
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        IntStream.range(1,10).forEach(i -> executorService.submit(() -> longAccumulator.accumulate(i)));
        executorService.shutdown();
        while(!executorService.isTerminated()){

        }
        System.out.println(longAccumulator.getThenReset());

        demo2();
    }

    public static void demo1(){
        LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0);
        longAccumulator.accumulate(1);
        longAccumulator.accumulate(2);
        System.out.println(longAccumulator.getThenReset());
    }

    public static void demo2(){
        LongAccumulator longAccumulator = new LongAccumulator((x,y) -> Math.max(x,y),0);
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        IntStream.range(1,10).forEach(i -> executorService.submit(() -> longAccumulator.accumulate(i)));
        executorService.shutdown();
        while(!executorService.isTerminated()){

        }
        System.out.println(longAccumulator.getThenReset());
    }
}

LongAccumulator类原理探究

LongAdder类是LongAccumulator的一个特例,LongAccumulator提供了比LongAdder更强大的功能,如下构造函数其中accumulatorFunction一个双目运算器接口,根据输入的两个参数返回一个计算值,identity则是LongAccumulator累加器的初始值。

public LongAccumulator(LongBinaryOperator accumulatorFunction,
					   long identity) {
	this.function = accumulatorFunction;
	base = this.identity = identity;
}
@FunctionalInterface
public interface LongBinaryOperator {

    /**
     * Applies this operator to the given operands.
     *
     * @param left the first operand
     * @param right the second operand
     * @return the operator result
     */
	 //根据两个参数计算返回一个值
    long applyAsLong(long left, long right);
}

LongAccumulator相比于LongAdder,可以为累加器提供非0的初始值,而LongAdder只能提供默认的0值。 另外,LongAccumulator还可以指定累加规则,比如累加或者相乘,只需要在构造LongAccumulator时,传入自定义的双目运算器即可,后者则内置累加规则。

LongAddr与LongAccumulator类相同点?

  • LongAddr与LongAccumulator类都是使用非阻塞算法CAS实现的,这相比于使用锁实现原子性操作在性能上有很大的提高。
  • LongAddr类是LongAccumulator类的一个特例,只是LongAccumulator提供了更强大的功能,可以让用户自定义计算规则。

参考: https://ifeve.com/java-atomic/

https://www.cnblogs.com/tong-yuan/p/Atomic.html

https://www.cnblogs.com/tong-yuan/p/LongAdder.html

https://cloud.tencent.com/developer/article/1466107

https://segmentfault.com/a/1190000015865714

标签:JUC,LongAdder,Java,Thread,int,原子,Atomic,new,public
From: https://blog.51cto.com/u_14014612/6029783

相关文章

  • Java并发JUC——CAS原理
    什么是CAS在计算机科学中,比较和交换(CompareAndSwap)是用于实现多线程同步的原子指令。它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为......
  • Java并发——final
    什么是不变性(Immutable)如果对象在被创建后,状态就不能被修改,那么它就是不可变的具有不变性的对象一定是线程安全的,我们不需要对其采取任何额外的安全措施,也能保证线程安......
  • Java并发JUC——并发容器
    引言容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集......
  • Java并发JUC——并发流程控制
    什么是并发流程控制控制并发流程的工具类,作用就是帮助我们程序员更容易的让线程之间进行合作让线程之间相互配合,来满足业务需求比如,让现场A等待线程B执行完毕后在执行等......
  • JavaS
    目录前言数据类型变量声明注释输出函数字符串运算if条件判断switch条件判断循环语句数组对象函数前言数据类型number:数字型,包括整数和小数boolean:布......
  • Java并发——ThreadLocal详解
    引言ThreadLocal的官方API解释为:“该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己......
  • java:分数-------简(Math类)
    Math类Math类中包含E和PI两个静态常量,正如它们名字所暗示的,它们的值分别等于e(自然对数)和π(圆周率)。例1调用Math类的E和PI两个常量,并将结果输出。......
  • Java concurrent并发工具包用户手册
    译序本指南根据JakobJenkov最新博客翻译,请随时关注博客更新:http://tutorials.jenkov.com/java-util-concurrent/index.html。本指南已做成中英文对照阅读版的pdf文档......
  • springboot,java,activiti实现流程审批(支持单体、微服务融合)
    前言activiti工作流引擎项目,企业erp、oa、hr、crm等企事业办公系统轻松落地,请假审批demo从流程绘制到审批结束实例。一、项目形式springboot+vue+activiti集成了activiti......
  • 如何通过Java应用程序添加或删除 PDF 中的附件
    当我们在制作PDF文件或者PPT演示文稿的时候,为了让自己的文件更全面详细,就会在文件中添加附件。并且将相关文档附加到PDF可以方便文档的集中管理和传输。那么如何添加或删......