【JUC】原子操作类

1、什么是原子操作类

原子性:无论有多少个操作,只要我们将这部分操作通过加锁的形式进行锁定,那么就可以视为一个原子性操作。原子性操作 = 线程安全。

原子操作类:提供了可以保证我们线程安全的类。可以直接使用。

当程序更新一个变量时,如果多线程同时更新这个变量,可能得到期望之外的值,比如变量i=1,A线程更新i+1,B线程也更新i+1,经过两个线程操作之后可能i不等于3,而是等于2(1.加锁,2. 定义我们的i变量为volatile形式。3. 可以使用我们的一个原子操作类。)。因为A和B线程在更新变量i的时候拿到的i都是1,这就是线程不安全的更新操作,通常我们会使用synchronized来解决这个问题,synchronized会保证多线程不会同时更新变量i。

而Java从JDK 1.5开始提供了java.util.concurrent.atomic包(以下简称Atomic包),这个包中的原子操作类提供了一种用法简单、性能高效(CAS)、线程安全地更新一个变量的方式。

因为变量的类型有很多种,所以在Atomic包里一共提供了13个类,属于4种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性(字段)。Atomic包里的类基本都是使用Unsafe实现的包装类。

2、原子更新基本类型

使用原子的方式更新基本类型,Atomic包提供了以下3个类。

  • AtomicBoolean:原子更新布尔类型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新长整型。

示例代码:BaseAtomicTest.java

import java.util.concurrent.atomic.AtomicInteger;

public class BaseAtomicTest {
    static AtomicInteger ai = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread a = new Thread(() -> {
            for(int i = 0; i < 5 ; i++) {
                ai.incrementAndGet(); //返回的增加1之后的值
            }
        });
        Thread b = new Thread(() -> {
            for(int i = 0; i < 7 ; i++) {
                ai.incrementAndGet(); //返回的增加1之后的值
            }
        });
        a.start();
        b.start();
        Thread.sleep(100); //为了保障 a b线程执行完毕之后,才继续执行后续的代码、
        System.out.println(ai.get()); // 获取ai的值
    }
}

3、原子更新数据类型

通过原子的方式更新数组里的某个元素,Atomic包提供了以下3个类。

  • AtomicIntegerArray:原子更新整型数组里的元素。
  • AtomicLongArray:原子更新长整型数组里的元素。
  • AtomicReferenceArray:原子更新引用类型数组里的元素。

AtomicIntegerArray类主要是提供原子的方式更新数组里的整型,

其常用方法如下。

  • int addAndGet(int i,int delta):以原子方式将输入值与数组中索引i的元素相加。
  • boolean compareAndSet(int i,int expect,int update):如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值。

示例代码:ArrayAtomicTest.java

import java.util.concurrent.atomic.AtomicIntegerArray;

public class ArrayAtomicTest {
    static int[] array = new int[2];
    public static void main(String[] args) {
        array[0] = 10;
        array[1] = 20;

        AtomicIntegerArray aia = new AtomicIntegerArray(array);
        aia.compareAndSet(0, 10, 11);
        System.out.println(aia.get(0)); // 11
        System.out.println(array[0]);// 10
        //我们的数组的原子操作类,只是复制了我们之前的array的对象,并不会
        //在操作中改变原有array的值。
        //原始的array只作为一个入参。真正保障我们的原子性的还是我们的AtomicIntegerArray
        /**
        public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();
    }
        */
        //备份的一份数据。
    }
}

4、原子更新引用类型

原子更新基本类型的AtomicInteger,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类。我们介绍一下两个常用类:

  • AtomicReference:原子更新引用类型。
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段。

示例代码:ReferenceAtomic.java

import java.util.concurrent.atomic.AtomicReference;

public class ReferenceAtomic {
    public static void main(String[] args) {
        AtomicReference<User> ar = new AtomicReference<>();
        User oldUser = new User("T",11);
        ar.set(oldUser);

        ar.compareAndSet(oldUser, new User("F",12));
        System.out.println(ar.get());
    }

    static class User {
        private String name;
        private Integer age;
        public User(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        @Override
        public String toString(){
            return name + ":" + age;
        }
    }
}

总结:对于原子更新数组类和原子更新对象引用类,他们实质上就是将我们的数组和对象的外层进行了一次 Atomic 的封装。所以,在进行替换的时候,其实是外层的这个封装保证了原子性。
想想我们的原子更新基本类型如:Integer。其实Integer不也是一个对象吗?也是在他的外层进行了一次封装。

5、原子更新字段类

如果需原子地更新某个类里的某个字段时,就需要使用原子更新字段类,Atomic包提供了以下3个类进行原子字段更新。

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。

要想原子地更新字段类需要两步。

第一步,因为原子更新字段类都是抽象类,每次使用的时候必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新类的字段(属性)必须使用public volatile修饰符。

以上3个类提供的方法几乎一样,所以本节仅以AstomicIntegerFieldUpdater为例进行讲解

示例代码:FieldAtomicTest.java

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class FieldAtomicTest {
    // 创建原子更新器,并设置需要更新的对象类和对象的属性
    private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
            newUpdater(User.class, "old");
    public static void main(String[] args) {
        User conan = new User("conan", 10);
        System.out.println(a.getAndIncrement(conan));// 10 该方法先获取老的值后+1
        System.out.println(a.get(conan));// 11
    }
    public static class User {
        private String name;
        public volatile int old;
        public User(String name, int old) {
            this.name = name;
            this.old = old;
        }
        public String getName() {
            return name;
        }
        public int getOld() {
            return old;
        }
    }

}

输出:

10 
11
posted @ 2022-12-15 16:22  DarkSki  阅读(34)  评论(0编辑  收藏  举报