Day296.原子类 -Juc

原子类

一、什么是原子类&作用

  • 不可分割

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

  • java.util.concurrent.atomic


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

    • 粒度更细:

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

    • 效率更高:

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


二、原子类纵览

Day296.原子类 -Juc

三、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);     } } 
Day296.原子类 -Juc
  • 给普通类型增加Day296.原子类 -Juc Day296.原子类 -Juc
    • 代码演示: getAndAdd()
    Day296.原子类 -Juc Day296.原子类 -Juc

    四、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);         }     } } 
    Day296.原子类 -Juc

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

    Day296.原子类 -Juc
    • 源码,compareAndSet()
    Day296.原子类 -Juc
    • 这里是之前的自旋锁演示例子
    /******  @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);     }      } 
    Day296.原子类 -Juc

    升级后的操作,都会直接作用到原来对象的属性上:所以直接 jDay296.原子类 -Juc

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

    • 注意点
      • 不支持被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();//自增             }         }     }  } 
      Day296.原子类 -Juc

      花费: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();//自增             }         }     }  } 
      Day296.原子类 -Juc

      花费:0.373s

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


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

    • AtomicLong每次加法,都需要flush和refresh,导致消耗资源更多。
    Day296.原子类 -Juc
    • LongAdder,每个线程他自己有独立的计数器
    Day296.原子类 -Juc

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

    Day296.原子类 -Juc

    3、Sum源码分析

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

    Day296.原子类 -Juc

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


    4、AtomicLong & LongAdder对比

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

    八、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());     } } 
    Day296.原子类 -Juc
    • 灵活使用,自定义表达式: 求1加到9中最大的数
    public class LongAccumulatorDemo {     public static void main(String[] args) {         //求1加到9中最大的数         LongAccumulator accumulator = new LongAccumulator((x, y) -> Math.max(x,y), 0);         ExecutorService pool = Executors.newFixedThreadPool(10);          //从1加到9         IntStream.range(1,10).forEach(i->pool.submit(()->accumulator.accumulate(i)));         pool.shutdown();          while (!pool.isTerminated()){ }         System.out.println(accumulator.getThenReset());     } } 
    Day296.原子类 -Juc
    • 使用场景
      • 并行计算,且不要求计算有顺序

版权声明:玥玥 发表于 2021-06-13 23:05:39。
转载请注明:Day296.原子类 -Juc | 女黑客导航