• 【并发编程】原子类


    1.什么是原子类

    (1)原子类简介

    • 一度认为原子是不可分割的最小单元,故原子类可以认为其操作都是不可分割的。

    (2)为什么要有原子类

    • 对多线程访问一个变量,我们需要加锁,而锁是比较消耗性能的,JDK1.5之后,新增的原子操作类提供了一种用法简单、性能高效、线程安全的更新一个变量的方式,这些同类位于JUC包下的atomic包下,发展到JDK1.8,该包下共有17个类,囊括了原子更新基本类型、原子更新属性、原子更新引用。

    (3)JDK1.8新增的原子类

    • DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64
    2.原子更新基本类型
    • JDK1.8基本类型原子类有以下分类

      • AtomicBoolean:原子更新布尔类型
      • AtomicInteger:原子更新整型
      • AtomicLong:原子更新长整型
      • DoubleAdder:对Double的原子更新性能进行优化提升
      • LongAdder:对Long的原子更新性能进行优化提升
      • DoubleAccumulator:支持自定义运算
      • LongAccumulator:支持自定义运算
    • AtomicInteger的常见用法

      • AtomicInteger(int initialValue):创建一个AtomicInteger实例,初始值由参数指定。不带参的构造方法初始值为0。
      • int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果,与getAndAdd(int delta)相区分,从字面意思即可区分,前者返回相加后结果,后者先返回再相加。
      • boolean compareAndSet(int expect, int update) :如果当前值等于预期值,则以原子方式将该值设置为输入的值。
      • int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
      • void lazySet(int newValue):最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
      • int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。
    • 案例实战

      • 线程不安全的例子
      public class Demo1 {
      
          private static Integer num = 0;
      
          void addUnSafe(){
              //对atomicInteger进行++操作
              num++;
          }
          public static void main(String[] args) throws InterruptedException {
      
              Demo1 demo1 = new Demo1();
      
              new Thread(()->{
                  for (int i = 0; i < 10; i++) {
                      for (int j = 0; j < 1000; j++) {
                          demo1.addUnSafe();
                      }
                  }
              }).start();
      
              new Thread(()->{
                  for (int i = 0; i < 10; i++) {
                      for (int j = 0; j < 1000; j++) {
                          demo1.addUnSafe();
                      }
                  }
              }).start();
              Thread.sleep(2000L);
              System.out.println("num最后的值:"+num);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

      在这里插入图片描述

      • AtomicInteger多线程调用
      public class Demo1 {
      
          private static AtomicInteger atomicInteger = new AtomicInteger(0);
      
          void addSafe(){
              //对atomicInteger进行++操作
              atomicInteger.incrementAndGet();
          }
          public static void main(String[] args) throws InterruptedException {
           	new Thread(()->{
                  for (int i = 0; i < 10; i++) {
                      for (int j = 0; j < 1000; j++) {
                          demo1.addSafe();
                      }
                  }
              }).start();
      
              new Thread(()->{
                  for (int i = 0; i < 10; i++) {
                      for (int j = 0; j < 1000; j++) {
                          demo1.addSafe();
                      }
                  }
              }).start();
      
              Thread.sleep(2000L);
              System.out.println("atomicInteger最后的值:"+atomicInteger.get());   
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29

      在这里插入图片描述

    • LongAccumulator的简单使用

    public class Demo2 {
    
        private static LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x*y,3L);
    
        public static void main(String[] args) {
            longAccumulator.accumulate(2);
            System.out.println(longAccumulator.get());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    3.原子更新数组类型
    • AtomicIntegerArray:对Integer数组类型的操作
    • AtomicLongArray:对Long数组类型的操作
    • AtomicReferenceArray:对对象数组类型的操作
    • AtomicIntegerArray的简单使用
    public class Demo1 {
    
        public static void main(String[] args) {
    
            int[] arrInt = new int[]{2,3,4};
    
            AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(arrInt);
    
            //给数组下标是1的元素+2
            int i = atomicIntegerArray.addAndGet(1, 2);
            System.out.println(i);
    
            //支持自定义规则的操作
            int i1 = atomicIntegerArray.accumulateAndGet(1, 2, (x, y) -> x > y ? x : y);
            System.out.println(i1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    4.原子地更新属性

    (1)原子的更新某个类的某个字段时,就需要使用原子更新字段类,Atomic包提供了一下4个类进行原子字段更新。

    • AtomicIntegerFieldUpdater:原子类int类型更改操作
    • AtomicLongFieldUpdater:原子类long类型更改操作
    • AtomicStampedReference:原子类引用类型更改操作,避免ABA问题,携带版本号
    • AtomicMarkableReference:原子类引用类型更改操作,避免ABA问题,携带boolean值
    • AtomicReferenceFieldUpdater:原子类引用类型更改操作

    (2)AtomicReferenceFieldUpdater、AtomicLongFieldUpdater案例实战

    public class Demo2 {
        public static void main(String[] args) {
            Student student = new Student(1L,"李祥");
            //对long类型的数值改变
            AtomicLongFieldUpdater<Student> atomicLongFieldUpdater = AtomicLongFieldUpdater.newUpdater(Student.class,"id");
            //将Student的id改成10
            atomicLongFieldUpdater.compareAndSet(student,1L,10L);
            //输出student的id
            System.out.println("更改student的id:"+student.id);
    
            //对引用类型的改变
            AtomicReferenceFieldUpdater<Student, String> referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
    
            referenceFieldUpdater.compareAndSet(student,student.getName(),"李祥更改");
    
            System.out.println("更改student的name:"+student.name);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    public class Student {
            volatile long id;
            volatile String name;
    
            public Student(long id, String name) {
                this.id = id;
                this.name = name;
            }
    
            public long getId() {
                return id;
            }
    
            public void setId(long id) {
                this.id = id;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述

    • 使用原子更新类时注意的问题:
      • 字段必须是volatile类型的,在线程之间共享变量时保证立即可见。
      • 字段的描述类型是调用者与操作对象字段的关系一致,也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。
      • 只能是实例变量,不能是类变量,也就是说不能加static关键字。
      • 不能被final变量修饰的。
      • 对于AtomicIntegerFiledUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其他包装类型(Integer\Long)。
      • 修改包装类型就需要使用AtomicRefebceFieldUpdater。

    (3)AtomicStampedReference使用

    • AtomciStampedReference是一个带有时间戳的对象引用,能够很好的解决CAS机制中的ABA问题。
    • 什么是ABA问题

    在这里插入图片描述

    • ABA场景案例
    public class ABADemo {
        private static AtomicInteger index = new AtomicInteger(10);
    
        public static void main(String[] args) {
            new Thread(()->{
                index.compareAndSet(10,11);
                index.compareAndSet(11,10);
                System.out.println(Thread.currentThread().getName()+": 进行了 10->11->10");
            },"张三").start();
    
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                    boolean flag = index.compareAndSet(10, 12);
                    System.out.println(Thread.currentThread().getName()+":修改index结果:"+flag+",设置的新值是:"+index);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"李祥").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述

    • AtomciStampedReference案例
    • compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp)。
      • 第一个参数expectedReference:表示预期值。
      • 第二个参数newReference:表示要更新的值。
      • 第三个参数expectedStamp:表示预期的时间戳。
      • 第四个参数newStamp:表示要更新的时间戳。
    public class AtomicStampedReferenceDemo {
    
        private static AtomicInteger index = new AtomicInteger(10);
    
        private static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference(10,1);
    
        public static void main(String[] args) {
    
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+":当前版本号为:"+stampedReference.getStamp());
                stampedReference.compareAndSet(10,11,stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+":当前版本号为:"+stampedReference.getStamp());
                stampedReference.compareAndSet(11,10,stampedReference.getStamp(),stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName()+":当前版本号为:"+stampedReference.getStamp());
            },"张三").start();
    
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+":拿到的版本号为:"+stampedReference.getStamp());
                    boolean flag = stampedReference.compareAndSet(10, 12, stampedReference.getStamp(), stampedReference.getStamp() + 1);
                    System.out.println(Thread.currentThread().getName()+":修改index结果:"+flag+",设置的新值是:"+stampedReference.getReference());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"李祥").start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    在这里插入图片描述

    • 其实除了AtomicStampedReference类,还有一个原子类也可以解决,就是AtomicMarkableReference,它不是维护一个版本号,而是维护一个boolean类型的标记,用法没有AtomicStampedReference灵活。因此也只是在特定的场景下使用。
  • 相关阅读:
    RK3568平台开发系列讲解(PCIE篇)PCIE驱动要如何学?
    关于qt中label挡住了dockwidget的窗体边缘
    SpringMVC(2)——请求与响应
    系统移植Makefile&README文件分析
    谋道翻译逆向
    NLP基本业务范围之二
    mysql交互下的命令
    使用ceph-deploy部署Ceph集群
    分布式日志部署
    【面试题】Java基础
  • 原文地址:https://blog.csdn.net/weixin_47533244/article/details/127804103