Atomic 原子类

时间:2021-09-17
本文章向大家介绍Atomic 原子类,主要包括Atomic 原子类使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

第四章 原子类

1、什么是原子类?

1、不可分割;

2、一个操作是不可中断的,即便是多线程的情况下也可以保证。

2、有什么作用?

1、原子类的作用和锁类似,是为了保证并发情况下线程安全。不过原子类相比于锁,有一定的优势:

  1)粒度更细:原子变量可以把竞争范围缩小到变量级别,这是我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度;

  2)效率更高:通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况。

3、六类原子类

1、六类原子类纵览

 2、基本类型原子类,以 AtomicInteger 为例

  1)常用方法:

    1. get():获取当前的值,返回 int 类型
    2. getAndSet(int newValue):获取当前的值,并设置新的值,返回 int 类型
    3. getAndIncrement():获取当前的值,并自增,返回 int 类型
    4. getAndDecrement():获取当前的值,并自减,返回 int 类型
    5. getAndAdd(int delta):获取当前的值,并加上预期的值,返回 int 类型
    6. compareAndSet(int expect,int update):如果输入的数值等于预期值,则以原子方式将该值设置为输入值,返回 boolean 类型

示例:

public class AtomicIntegerDemo1 {
    private static  final AtomicInteger atomicInteger = new AtomicInteger();
    private static ExecutorService service = Executors.newFixedThreadPool(5);

    public static void incrementAtomic() {
        //自减
        //atomicInteger.getAndDecrement();
        //自增
        atomicInteger.getAndIncrement();
        //如果值为100,则修改他为 8
        atomicInteger.compareAndSet(100,8);
    }

    public static void main(String[] args) {
        for (int i = 0; i <100 ; i++) {
            int finalI = i;
            service.execute(() -> {
            incrementAtomic();
        });
        }
        service.shutdown();
        while(true){
            if(service.isTerminated()){
                System.out.println(atomicInteger.get());
                break;
            }
        }
    }
}

2、数组类型原子类,以 AtomicIntegerArray 为例

 演示:

public class AtomicArrayDemo {
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(100);
        //调用自增的线程
        Thread[] threads1 = new Thread[100];
        //调用自减的线程
        Thread[] threads2 = new Thread[100];
        //初始化线程
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            threads1[i] = new Thread(() -> decrement(atomicIntegerArray, finalI));
            threads2[i] = new Thread(() -> increment(atomicIntegerArray, finalI));
        }
        //调用自增自减,按照原子性的特点,数组中的所有值都应该为 0
        for (int i = 0; i < 100; i++) {
            threads1[i].start();
            threads2[i].start();
        }
        Thread.sleep(5000);
        for (int i = 0; i < 100; i++) {
            System.out.println(atomicIntegerArray.get(i));
            if (atomicIntegerArray.get(i) != 0)
                System.out.println("结果不为 0 就错了!!!");
        }
    }

    //自减
    public static void decrement(AtomicIntegerArray array,int count){
        array.getAndDecrement(count);
    }

    //自增
    public static void increment(AtomicIntegerArray array,int count){
        array.getAndIncrement(count);
    }
}

运行结果都为: 0

3、引用类型原子类,以 AtomicReference 为例

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

演示:

public class AtomicReferenceDemo2 {
    private static final AtomicReference atomicReference = new AtomicReference();

    public static void getAndSet(Student user){
        atomicReference.getAndSet(user);
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            getAndSet(new Student("张三"));
            System.out.println(atomicReference.get());
        },"Thread-01").start();

        new Thread(() -> {
            getAndSet(new Student("李四"));
            System.out.println(atomicReference.get());
        },"Thread-02").start();

        Thread.sleep(2000);
        System.out.println(atomicReference.get());
    }
}

class Student{
    public String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Student() {
    }

    public Student(String name) {
        this.name = name;
    }
}

4、把普通变量升级为具有原子功能

1、AtomicIntegerFieldUpdater 对普通变量进行升级,升级后具有原子性

示例:

public class AtomicIntegerFieldUpdaterDemo3 {

    public static class Candidate{
        //要升级的变量不能用 static 和 private 修饰,否则报错
        volatile int score;
    }

    static Candidate tom;
    static Candidate peter;
    //利用反射原理
    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");

    public static void main(String[] args) throws InterruptedException {
        tom = new Candidate();
        peter = new Candidate();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
            tom.score++;
            scoreUpdater.getAndIncrement(peter);
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
            tom.score++;
            scoreUpdater.getAndIncrement(peter);
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("升级后的值:"+peter.score);
        System.out.println("原本的值:"+tom.score);
    }
}

运行结果:

2、AtomicIntegerFieldUpdater 注意点

  1)可见范围(要升级的变量不能使用 private 修饰,否则报错);

  2)不支持 static 修饰(要升级的变量不能使用 static修饰,否则报错)。

3、Adder 累加器

  1)是 Java8 引入的;

  2)高并发下 LongAdder 比 AtomicLong 效率高,不过本质是空间换时间;

  3)竞争激烈的时候,LongAdder 把不同线程对应到不同的 cell 上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性。

LongAdder 示例:

public class LongAdderDemo5 {
    public static void main(String[] args) {
        LongAdder counter = new LongAdder();
        ExecutorService service = Executors.newFixedThreadPool(10);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new Task(counter));
        }
        service.shutdown();
        while(!service.isTerminated()){

        }
        long end = System.currentTimeMillis();
        System.out.println(counter.sum());
        System.out.println("LongAdder耗时:" + (end-start));
    }
    private 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();
            }
        }
    }
}

运行结果:

AtomicLong 示例:

public class AtomicLongDemo4 {
    public static void main(String[] args) {
        AtomicLong counter = new AtomicLong(0);
        ExecutorService service = Executors.newFixedThreadPool(10);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new Task(counter));
        }
        service.shutdown();
        while(!service.isTerminated()){

        }
        long end = System.currentTimeMillis();
        System.out.println(counter.get());
        System.out.println("AtomicLong耗时:" + (end-start));
    }
    private 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();
            }
        }
    }
}

运行结果

根据运行结果知道,再高并发情况下 LongAdder 耗时 245 毫秒,AtomicLong 耗时 1616 毫秒。

4、AtomicLong 和 LongAdder

  1)再低争用下,AtomicLong 和 LongAdder 这两个类具有相似的特征。但是在竞争激烈的情况下,LongAdder 的预期吞吐量要高的多,但要消耗更多的空间;

  2)LongAdder 适合的场景是统计和计数的场景,而且 LongAdder 基本只提供了 add 方法,而 AtomicLong 还具有 CAS 方法。

5、Accumulator 累加器

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

示例:

public class LongAccumulatorDemo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(5);
        /*
        x初始为0,y的值由accumulator.accumulate(y)设定
        调用accumulator.accumulate(y)后计算结果,并把结果赋给x,可以继续调用accumulator.accumulate(y)来继续进行计算
        调用accumulator.getThenReset()来获取当前计算出的结果
        */
        LongAccumulator accumulator = new LongAccumulator((x,y) -> 2*(x+y),0);
         
        for (int i = 0; i < 10; i++) {
        service.execute(() -> {
            accumulator.accumulate(2);
            });
        }
        service.shutdown();
        while(!service.isTerminated()){

        }
        System.out.println(accumulator.getThenReset());//4092
    }
}

Accumulator 适用于并发情况下使用,效率更高,如果单线程计算的话,可以使用 for 循环计算。

原文地址:https://www.cnblogs.com/nicechen/p/15303211.html