一、前言
原子类主要用于并发编程里操作原子数据时使用到,位于util.concurrent.atomic包下。
我们可以通过下图看到,在JDK中已经定义了很多个原子类:
我们可以根据其功能把常用的一些原子类进行分类:
- 原子更新基本数据类型:
- AtomicBoolean :原子更新布尔类型
- AtomicInteger :原子更新整型
- AtomicLong :原子更新长整型
- 原子更新数组类型:
- AtomicIntegerArray :原子更新整型数组里的元素
- AtomicLongArray :原子更新长整型数组里的元素
- AtomicReferenceArray :原子更新引用类型数组里的元素
- 原子更新引用数据类型:
- AtomicReference :原子更新引用类型
- AtomicReferenceFieldUpdater :原子更新引用类型的字段
- AtomicMarkableReferce :原子更新带有标记位的引用类型,可以使用构造方法更新一个布尔类型的标记位和引用类型
- 原子更新字段类型:
- AtomicIntegerFieldUpdater :原子更新整型的字段的更新器
- AtomicLongFieldUpdater :原子更新长整型字段的更新器
- AtomicStampedFieldUpdater :原子更新带有版本号的引用类型
二、常用方法整理
由于类中许多方法功能类似,这里进行分类列举,把常用的方法写出来先了解,源码部分后面有机会再详细研究,第一阶段,先知道怎么用~
原子更新基本数据类型
以 AtomicInteger 为例,常用的方法有:
- int addAndGet(int delta) : 以原子的方式将输入的数值与实例中的值相加,并返回结果。
- boolean compareAndSet(int expect, int update) :如果输入的值等于预期值,则以原子方式将该值设置为输入的值。
- int getAndIncrement() :以原子的方式将当前值加 1。PS:这里返回的是自增前的值,也就是旧值。
- void lazySet(int newValue) :最终会设置成newValue,使用lazySet设置值后,可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
- int getAndSet(int newValue) :以原子的方式设置为newValue,并返回旧值。
写一个测试类来简单看下这些方法怎么使用:
/** * 原子更新基本类型测试类【AtomicInteger为例】 * * @author 有梦想的肥宅 */ public class AtomicTest { public static AtomicInteger ai = new AtomicInteger(1); public static void main(String[] args) { System.out.println("===== 1、先原子相加,再返回结果 ====="); System.out.println(ai.addAndGet(2)); System.out.println("===== 2、输入当前值【第一个参数】,比较成功后返回true并设置值为【第二个参数】,否则返回false ====="); System.out.println(ai.compareAndSet(3, 2)); System.out.println("===== 3、获取旧值并+1 ====="); System.out.println(ai.getAndIncrement()); System.out.println("===== 4、后置赋值,赋值期间可能会被其他线程获取到旧值 ====="); ai.lazySet(7); System.out.println(ai.get()); System.out.println("===== 5、原子方式设置为新的值,并返回旧的值 ====="); System.out.println(ai.getAndSet(10)); System.out.println("===== 6、获取当前原子数值 ====="); System.out.println(ai.get()); } }
原理:
- 底层代码的compareAndSwapInt()方法,使用了CAS原理【比较和交换】去控制。【CAS详解看这里】
原子更新数组类型
以 AtomicIntegerArray 为例,常用的方法有:
- set(int index,int value) :将位置 i 的元素设置为给定值,默认值为0
- get(int index) : 获取索引为index的元素值。
- addAndGet(int index,int value) :以原子方式先对给定下标加上特定的值,再获取相加后的值
- incrementAndGet(int index) :以原子方式先对下标加1再获取值
- compareAndSet(int i, int expect, int update) : 如果当前值等于预期值,则以原子方式将数组位置 i 的元素设置为update值。
写一个测试类来简单看下这些方法怎么使用:
/** * 原子更新数组类型测试类【AtomicIntegerArray为例】 * * @author 有梦想的肥宅 */ public class AtomicTest2 { public static int[] value = new int[]{1, 2, 3, 4, 5}; public static AtomicIntegerArray aiArray = new AtomicIntegerArray(value); public static void main(String[] args) { System.out.println("===== 1、将位置 i 的元素设置为给定值,默认值为0 ====="); aiArray.set(0, 10); System.out.println(aiArray.get(0)); System.out.println("===== 2、以原子方式先对给定下标加上特定的值,再获取相加后的值 ====="); System.out.println(aiArray.addAndGet(0, 2)); System.out.println("===== 3、如果当前值【第一个参数】 == 预期值,则以原子方式将位置 i 的元素设置为给定的更新值【第二个参数】 ====="); System.out.println(aiArray.compareAndSet(0, 12, 6)); System.out.println("===== 4、以原子方式先对下标加1再获取值 ====="); System.out.println(aiArray.incrementAndGet(0)); } }
原子更新引用数据类型
原子更新基本类型,只能更新一个值。如果更新多个值,比如更新一个对象里的值,那么就要用原子更新引用类型提供的类,我们直接写一个测试类来看看怎么使用:
/** * 原子更新引用类型测试类 * * @author 有梦想的肥宅 */ public class AtomicTest3 { public static AtomicReference<User> aiReference = new AtomicReference<User>(); public static void main(String[] args) { //1、原子引用内放入u1对象 User u1 = new User("zh", 26, "男"); aiReference.set(u1); System.out.println(aiReference.get().getName() + aiReference.get().getAge() + aiReference.get().getSex()); //2、比较原子引用内的对象,如果相同则替换为新对象 User u2 = new User("zh", 26, "女"); aiReference.compareAndSet(u2, u2); System.out.println(aiReference.get().getName() + aiReference.get().getAge() + aiReference.get().getSex()); //3、传入的预期对象【第一个参数】与当前原子引用类型内的对象完全一致时,则更新为新的对象 User u3 = new User("wlx", 27, "女"); aiReference.compareAndSet(u1, u3); System.out.println(aiReference.get().getName() + aiReference.get().getAge() + aiReference.get().getSex()); } /** * 用户基本信息 */ @Data public static class User { private String name; private int age; private String sex; public User(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } } }
原理:
- 底层代码的compareAndSwapObject()方法,使用了CAS原理【比较和交换】去控制。【CAS详解看这里】
原子更新字段类型
原子更新字段类型主要是用在更新一个对象里面的某个字段的场景,我们也是直接上代码来搞一波:
/** * 原子更新字段类型测试类 * * @author 有梦想的肥宅 */ public class AtomicTest4 { //创建原子更新器,并设置需要更新的对象类和对象的属性//AtomicIntegerFieldUpdater.newUpdater:第一个参数【对象的类型】,第二个参数【需要更新的字段】 private static AtomicIntegerFieldUpdater<User> aiFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age"); public static void main(String[] args) { //1、创建一个用户对象 User u1 = new User("zh", 26, "男"); //2、使用原子字段更新类更新年龄,+1 System.out.println(aiFieldUpdater.getAndIncrement(u1)); //3、输出更新后的年龄 System.out.println(u1.getAge()); } /** * 用户基本信息 */ @Data public static class User { private String name; volatile int age; //PS:需要被原子更新的字段不能设置为private,且要加上volatile关键字来修饰 private String sex; public User(String name, int age, String sex) { this.name = name; this.age = age; this.sex = sex; } } }
注意:
- 1、需要被原子更新的字段不能设置为private
- 2、需要被原子更新的字段要加上volatile关键字来修饰
参考文章: