【并发编程】原子类

简介: 【并发编程】原子类

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);
}

a387d01298934e81b29927f678ae41c0.jpg

  • 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());   
    }
}

893ac9cccfc9404e98b5e00ad7695a8c.jpg

  • 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());
    }
}

447831f8be6247c59f15cf9a79b408db.jpg

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);
    }
}

1834022091524860930fcd666e4a4959.jpg

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);
    }
}
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;
        }
}


eedd07161f6b4cf497c790cbcd3af87a.jpg

使用原子更新类时注意的问题:

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

(3)AtomicStampedReference使用

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

f5251ebf766942f7bfd6791e8ffb8a49.jpg

  • 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();
    }
}

24617cdc4d2b4fb9af4eef6c8ca14bbb.jpg

  • 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();
    }
}


2709785b620747be9c59c70536c2e461.jpg

其实除了AtomicStampedReference类,还有一个原子类也可以解决,就是AtomicMarkableReference,它不是维护一个版本号,而是维护一个boolean类型的标记,用法没有AtomicStampedReference灵活。因此也只是在特定的场景下使用。


相关文章
|
6月前
|
缓存 Java 编译器
JUC 并发编程之JMM
Java内存模型是Java虚拟机(JVM)规范中定义的一组规则,用于屏蔽各种硬件和操作系统的内存访问差异,保证多线程情况下程序的正确执行。Java内存模型规定了线程之间如何交互以及线程和内存之间的关系。它主要解决的问题是可见性、原子性和有序性。
|
3月前
|
存储 Java
JUC(8)JMM
这篇文章介绍了Java内存模型(JMM),解释了volatile关键字的作用,包括确保变量的可见性、禁止指令重排但不保证操作的原子性,并探讨了单例模式的实现方式,包括饿汉式和懒汉式单例模式的示例代码。
JUC(8)JMM
|
Java
并发编程——ReentrantLock
Java中提供锁,一般就是synchronized和lock锁,ReentrantLock跟synchronized一样都是互斥锁。如果竞争比较激烈,推荐lock锁,效率更高。如果几乎没有竞争,推荐synchronized。
28 0
|
6月前
|
安全 Java
JUC并发编程之原子类
并发编程是现代计算机应用中不可或缺的一部分,而在并发编程中,处理共享资源的并发访问是一个重要的问题。为了避免多线程访问共享资源时出现竞态条件(Race Condition)等问题,Java提供了一组原子类(Atomic Classes)来支持线程安全的操作。
|
6月前
|
缓存 安全 Java
JUC并发编程之volatile详解
Java内存模型是Java虚拟机(JVM)规范中定义的一组规则,用于屏蔽各种硬件和操作系统的内存访问差异,保证多线程情况下程序的正确执行。Java内存模型规定了线程之间如何交互以及线程和内存之间的关系。它主要解决的问题是可见性、原子性和有序性。
|
6月前
|
缓存 算法 Java
JUC并发编程之CAS
CAS,即Compare and Swap,是一种并发编程中用于实现多线程环境下的原子操作的技术。它是一种无锁算法,用于解决多线程环境下的数据同步问题。CAS操作包含三个操作数:内存位置V,旧的预期值A和即将要写入的新值B。只有当内存位置的值与旧的预期值A相等时,才会将新值B写入内存位置V,否则不执行任何操作。CAS操作是原子的,保证了多线程环境下的数据一致性和线程安全性。
|
安全 Java
并发编程系列教程(11) - 原子类
并发编程系列教程(11) - 原子类
43 0
|
Java
并发编程(六)ReentrantLock
并发编程(六)ReentrantLock
113 0
|
缓存 Java 编译器
juc并发编程02——JMM模型(上)
我们在这篇文章中将介绍JMM模型,也就是java内存模型。注意,本文所提到的JMM模型与JVM内存模型属于不同层次的内容。JVM内存模型讲的是物理内存空间的分配,而JMM则强调对于JVM内存模型的抽象。
juc并发编程02——JMM模型(上)
|
缓存 Java 程序员
juc并发编程02——JMM模型(下)
我们在这篇文章中将介绍JMM模型,也就是java内存模型。注意,本文所提到的JMM模型与JVM内存模型属于不同层次的内容。JVM内存模型讲的是物理内存空间的分配,而JMM则强调对于JVM内存模型的抽象。
juc并发编程02——JMM模型(下)