Day296.原子类 -Juc

Posted 阿昌喜欢吃黄桃

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day296.原子类 -Juc相关的知识,希望对你有一定的参考价值。

原子类

一、什么是原子类&作用

  • 不可分割

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

  • java.util.concurrent.atomic


  • 作用类似与锁,为保证并发情况下线程安全。原子类相比锁更具有优势

    • 粒度更细:

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

    • 效率更高:

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


二、原子类纵览

image-20210612175045781


三、Atomic*基本类型原子类

包含:AtomicInteger、AtomicLong、AtomicBoolean

以AtomicInteger为例子

1、常用方法

  • 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)

  • 代码演示: 原子类和普通类的对比

/******
 @author 阿昌
 @create 2021-06-12 18:04
 *******
 *      演示AtomicInteger的基本用法,并对比非原子类的线程安全问题
 */
public class AtomicIntegerDemo1 implements Runnable {
    private static final AtomicInteger atomicInteger =  new AtomicInteger();


    //原子类型自增
    public void atomicIncrement(){
        atomicInteger.getAndIncrement();
    }

    private static volatile int basicCount = 0;

    //普通类型自增
    public void basicIncrement(){
        basicCount++;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            atomicIncrement();
            basicIncrement();
        }
    }

    //主函数
    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerDemo1 aid = new AtomicIntegerDemo1();
        Thread thread1 = new Thread(aid);
        Thread thread2 = new Thread(aid);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

        System.out.println("原子类的结果:"+atomicInteger.get());
        System.out.println("普通变量值:"+basicCount);
    }
}

image-20210612181157784

  • 给普通类型增加synchronized修饰

image-20210612181324269

image-20210612181329614


  • 代码演示: getAndAdd()

image-20210612181651572

image-20210612181656769


四、Atomic*Array数组类型原子类

/******
 @author 阿昌
 @create 2021-06-12 18:17
 *******
 *      演示原子数组的使用方法
 */
public class AtomicArray {

    public static void main(String[] args) {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);

        Incrementer incrementer = new Incrementer(atomicIntegerArray);
        Decrementer decrementer = new Decrementer(atomicIntegerArray);

        Thread[] threadsIncrementer = new Thread[100];
        Thread[] threadsDecrementer = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threadsDecrementer[i] = new Thread(decrementer);
            threadsIncrementer[i] = new Thread(incrementer);

            threadsDecrementer[i].start();
            threadsIncrementer[i].start();
        }

//        Thread.sleep(10000);
        for (int i = 0; i < 100; i++) {
            try {
                threadsDecrementer[i].join();
                threadsIncrementer[i].join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

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

    }
}

//自减任务类
class Decrementer implements Runnable{
    private AtomicIntegerArray array;

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

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

//自增任务类
class Incrementer implements Runnable{
    private AtomicIntegerArray array;

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

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

image-20210612183458105


五、Atomic*Reference引用类型原子类

image-20210612183606036

  • 源码,compareAndSet()

image-20210612183935537

  • 这里是之前的自旋锁演示例子
/******
 @author 阿昌
 @create 2021-06-11 21:10
 *******
 *      自旋锁演示
 */
public class SpinLock {
    private AtomicReference<Thread> sign = new AtomicReference<>();

    //加锁操作
    public void lock(){
        Thread current = Thread.currentThread();
        //期待是null,如果是期望的,就将其设置为current
        while (!sign.compareAndSet(null,current)){
            System.out.println(Thread.currentThread().getName()+":自旋获取失败,再次尝试");
        }
    }

    //解锁操作
    public void unlock(){
        Thread current = Thread.currentThread();
        //期待加锁的当前线程,如果是期望的,就将其设置为为null,也就是没有持有了,就是解锁了
        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(300);
                } 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();

    }

}

六、AtomicIntegerFieldUpdater升级原子操作

  • AtomicIntegerFieldUpdater对普通变量进行升级

  • 使用场景

    • 偶尔需要一个原子get/set操作(如晚上某个时刻他存在大量并发修改,其他时刻就正常)
    • 这个变量我们无法操作,只能对他进行升级
  • 代码演示

/******
 @author 阿昌
 @create 2021-06-12 19:02
 *******
 *      演示AtomicIntegerFieildUpdater的用法
 */
public class AtomicIntegerFieildUpdater implements Runnable {
    static Candidate tom;
    static Candidate jack;

    //newUpdater():参数1指定哪个类,参数2哪个属性
    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            tom.score++;//普通自增
            scoreUpdater.getAndIncrement(jack);//通过包装自增
        }
    }


    //候选人类
    public static class Candidate{
        //分数
        volatile int score;
    }

    //主函数
    public static void main(String[] args) throws InterruptedException {
        tom = new Candidate();
        jack = new Candidate();
        AtomicIntegerFieildUpdater a = new AtomicIntegerFieildUpdater();
        Thread thread1 = new Thread(a);
        Thread thread2 = new Thread(a);

        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

        System.out.println("普通自增: "+tom.score);
        System.out.println("升级自增: "+jack.score);
    }
    
}

image-20210612191140721

升级后的操作,都会直接作用到原来对象的属性上:所以直接 jack.score就可


image-20210612191446256

他让我们传入类,和对应属性名,这里就可以感觉到他使用的底层原理是反射

  • 注意点
    • 不支持被static修饰的变量
    • 可见范围,由public修饰的变量,private不行

七、Adder累加器

  • Java8引入

  • 高并发下LongAdder比AtomicLong效率高,本质还是空间换时间

  • 竞争激烈的情况下,LongAdder会把不同线程对应到不同的Cell上进行修改,降低冲突的概率,是多段锁的理念,提高了并发性

1、对比AddderLong & AtomicLong的高并发性能

  • AtomicLong,20个线程并发,每个线程执行10000次

    public class AtomicLongDemo {
        //主函数
        public static void main(String[] args) throws InterruptedException {
            AtomicLong counter = new AtomicLong(0);
    
            //新建线程池
            ExecutorService pool = Executors.newFixedThreadPool(20);
            long startTime = System.currentTimeMillis();
            //任务次数
            for (int i = 0; i < 10000; i++) {
                pool.submit(new Task(counter));
            }
            //关闭线程池
            pool.shutdown();
            while (!pool.isTerminated()){
            }
            long endTime = System.currentTimeMillis();
            System.out.println(counter.get());
            System.out.println("AtomicLong完成时间:"+(endTime-startTime)+"毫秒");
        }
    
        //任务内部类
        public static class Task implements Runnable{
            private AtomicLong count;
    
            public Task(AtomicLong count) {
                this.count = count;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    count.incrementAndGet();//自增
                }
            }
        }
    
    }
    

    image-20210612193506193

    花费:1.959s


  • LongAdder,20个线程并发,每个线程执行10000次

    public class LongAdderDemo {
        //主函数
        public static void main(String[] args) throws InterruptedException {
            LongAdder counter = new LongAdder();
    
            //新建线程池
            ExecutorService pool = Executors.newFixedThreadPool(20);
            long startTime = System.currentTimeMillis();
            //任务次数
            for (int i = 0; i < 10000; i++) {
                pool.submit(new Task(counter));
            }
            //关闭线程池
            pool.shutdown();
            while (!pool.isTerminated()){
            }
            long endTime = System.currentTimeMillis();
            System.out.println(counter.sum());
            System.out.println("LongAdder完成时间:"+(endTime-startTime)+"毫秒");
        }
    
        //任务内部类
        public static class Task implements Runnable{
            private LongAdder count;
    
            public Task(LongAdder count) {
                this.count = count;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    count.increment();//自增
                }
            }
        }
    
    }
    

    image-20210612193612601

    花费:0.373s

总结在多线程的情况下,LongAdder比AtomicLong的性能更好


2、为什么AdderLong高并发性能好的原因

  • AtomicLong每次加法,都需要flush和refresh,导致消耗资源更多。

image-20210612194804032


  • LongAdder,每个线程他自己有独立的计数器

image-20210612195800090

那这里我就觉得就会出现最后线程不安全的情况,无法保持一致性,那这里就要讲一下他最后的Sum汇总阶段

image-20210612200055346


3、Sum源码分析

他最后会判断,如果有as变量也就是cell[]数组,他就跟base一起相加结果返回最后的值

image-20210612200228654

上面的源码看出,这个遍历相加的内部没有保证线程安全,也就是说如果之前加好的数组元素发生了变动,他就不会实时最新的反应在最终返回的sum总和中,也就是说返回的sum结果可能不是最新的值


4、AtomicLong & LongAdder对比

  • LongAdder
    • 消耗更多的空间;
    • 在高并发的情况下性能更好;
    • 适用于统计求和计数场景;
    • 类方法相对较少

image-20210612200807515


八、Accumulator累加器

  • 类似与LongAdder,功能更强劲

  • 代码演示:基本用法

public class LongAccumulatorDemo {
    public static void main(String[] args) {
        //参数1:表达式
        //参数2:初始值,对X的第一次定义
        //最开始会将初始值赋给X ,y就是之前的结果;类似于 数学归纳法
        LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 100);
        accumulator.accumulate(1);//此时,x=1,y=100,结果为101
        accumulator.accumulate(2);//此时,x=2,y=101,结果为103
        System.out.println(accumulator.getThenReset());
    }
}

image-20210612201615949<

以上是关于Day296.原子类 -Juc的主要内容,如果未能解决你的问题,请参考以下文章

java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理

java并发 day04CAS 原子整数 原子引用 原子数组 字段更新器和原子累加器 unsafe CPU缓存结构 不可变类 final的原理

Day823.Java原子性问题解决方案 -Java 并发编程实战

Day823.Java原子性问题解决方案 -Java 并发编程实战

Day755.Redis的原子操作 -Redis 核心技术与实战

Python_Note_Day 10_Coroutine