Atomic原子类大家族
1、Atomic原子类家族基本介绍
1.1 你真的知道什么是线程安全吗?
初看『线程安全』这几个字,很容易望文生义,这不就是线程的安全吗?
其实不是,线程本身没有好坏,没有『安全的线程』和『不安全的线程』之分!
俗话说:人之初性本善,线程天生也是纯洁善良的,真正让线程变坏的原因是访问的变量,变量对于操作系统来说其实就是内存块,所以绕了这么一大圈,线程安全称为『内存安全』可能更为贴切!
简而言之,线程访问的内存决定了这个线程是否是安全的!
变量大致可以分为局部变量和共享变量,局部变量对于 JVM 来说是栈空间,大家都背过八股文,栈是线程私有的、非共享的,那自然也是内存安全的;共享变量对于 JVM 来说一般是存在于堆上,堆上的东西是所有线程共享的,如果不加任何限制自然是不安全的!
因为线程安全这个概念已经深入人心了,所以后面我们还是用线程安全来表达内存安全的含义。
那如何解决这种 不安全
呢?方法有很多,比如:加锁、Atomic原子类等。
1.2 什么是 Atomic原子类?
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可仔分割的。在Java中, Atomic 指一个操作是不可中断的,即使是多个线程环境中,某个线程的一个操作一旦开始,就不会被其他线程干扰!
所以,所谓Atomic原子类说简单点就是具有原子操作特征的类,其底层原理是利用 CAS
来确保对变量操作的原子性!
JUC中的Atomic原子类都存放在java.util.concurrent.atomic
包下:
根据操作的数据类型,可以将 JUC 包中的原子类分为 4 类:
原子更新基本类型:使用原子的方式更新基本类型
AtomicInteger
:整型原子类AtomicLong
:长整型原子类AtomicBoolean
:布尔型原子类
原子更新数组类型:使用原子的方式更新数组里的某个元素
AtomicIntegerArray
:整型数组原子类AtomicLongArray
:长整型数组原子类AtomicReferenceArray
:引用类型数组原子类
原子更新引用类型:使用原子的方式更新引用类型
AtomicReference
:引用类型原子类AtomicMarkableReference
:原子更新带有标记的引用类型AtomicStampedReference
:原子更新带有版本号的引用类型该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
原子更新更新属性:使用原子的方式更新对象的某个字段
AtomicIntegerFieldUpdater
:原子更新整型字段的更新器AtomicLongFieldUpdater
:原子更新长整型字段的更新器AtomicReferenceFieldUpdater
:原子更新引用类型里的字段
1.3 小试牛刀
递增运算 count++ 不是一个原子操作,在多线程环境下并不能得到正确的结果,因为 count++ 操作实际上分为三个步骤:
- 读取 count 变量的值;
- 将 count 变量的值加 1;
- 将 count 变量的值写入到内存中;
假定线程 A 正在修改 count 变量,为了保证线程 B 在使用 count 的时候是线程 A 修改过后的状态,可以用 synchronized 关键字同步:
private long count = 0;
public synchronized void plus() {
count++;
}
但多个线程之间访问 plus()
方法是互斥的,线程 B 访问的时候必须要等待线程 A 访问结束,有没有更好的办法呢?
我们知道,Synchronized(未优化前) 最主要的问题是:当发生线程竞争共享资源的情况时,会出现线程阻塞和等待唤醒带来的性能开销,因为这是一种互斥同步(阻塞同步),而CAS并不是武断的将未得到锁的线程挂起,当CAS操作失败后会进行一定的尝试(即自旋,无非就是一个死循环),而非进行耗时的挂起、唤醒操作,因此也叫做非阻塞同步!
AtomicInteger 是 JDK 提供的一个原子操作的 Integer 类,其底层原理是利用 CAS
来确保对变量操作的原子性,它提供的加减操作是线程安全的。于是我们可以这样:
private AtomicInteger count = new AtomicInteger(0);
public void plus() {
count.incrementAndGet();
}
你看,这下是不是就舒服多了,不用加锁,也能保证线程安全,并且还降低了性能开销!
1.4 AtomicInteger的线程安全原理简单解析
为了能够弄懂 AtomicInteger 的实现原理,以 incrementAndGet 方法为例,来看下源码:
public final int incrementAndGet() {
// 使用Unsafe类中的getAndAddInt方法原子地增加AtomicInteger的当前值
// 第一个参数this是AtomicInteger的当前实例
// 第二个参数valueOffset是一个偏移量,它指示在AtomicInteger对象中的哪个位置可以找到实际的int值
// 第三个参数1表示要加到当前值上的值(即增加的值)
// 整个getAndAddInt(this, valueOffset, 1)返回的是增加前的旧值,那么最后一步+1,表示将要返回增加后的新值
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
可以看出,该方法实际上是调用了 unsafe 对象的 getAndAddInt 方法,unsafe 对象是通过通过 UnSafe 类的静态方法 getUnsafe 获取的:
private static final Unsafe unsafe = Unsafe.getUnsafe();
Unsafe 类前面也讲过,是 Java 中的一个特殊类,用于执行低级、不安全的操作。getAndIncrement 方法就是利用了 Unsafe 类提供的 CAS(Compare-And-Swap)操作来实现原子的 increment 操作。CAS 是一种常用的无锁技术,允许在多线程环境中原子地更新值!
2、原子更新基本类型
2.1 有哪些原子更新基本类型
在多线程环境下,如果需要更新某个变量,可以使用原子更新基本类型:
AtomicInteger
:整型原子类AtomicLong
:长整型原子类AtomicBoolean
:布尔型原子类
这几个类的用法基本一致,这里以 AtomicInteger 为例来介绍!
2.1 AtomicInteger类常用方法
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)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
2.2 AtomicInteger类使用示例
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) {
int temp = 0;
AtomicInteger i = new AtomicInteger(0);
temp = i.getAndSet(3);
System.out.println("temp:" + temp + "; i:" + i); //temp:0; i:3
temp = i.getAndIncrement();
System.out.println("temp:" + temp + "; i:" + i); //temp:3; i:4
temp = i.getAndAdd(5);
System.out.println("temp:" + temp + "; i:" + i); //temp:4; i:9
}
}
运行结果:
temp:0; i:3
temp:3; i:4
/temp:4; i:9
3、原子更新数组类型
3.1 有哪些原子更新数组类型
在多线程环境下,如果需要更新数组里的某个元素,可以使用原子更新数组类型:
AtomicIntegerArray
:整形数组原子类AtomicLongArray
:长整形数组原子类AtomicReferenceArray
:引用类型数组原子类
这几个类的用法基本一致,这里以 AtomicIntegerArray 为例来介绍!
3.2 AtomicIntegerArray类常用方法
public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
3.3 AtomicIntegerArray类使用示例
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) {
int temp = 0;
int[] nums = { 1, 2, 3, 4, 5, 6 };
AtomicIntegerArray i = new AtomicIntegerArray(nums);
for (int j = 0; j < nums.length; j++) {
System.out.println(i.get(j)); // 1,2,3,4,5,6
}
temp = i.getAndSet(0, 2);
System.out.println("temp:" + temp + "; i:" + i); //1;2,2,3,4,5,6
temp = i.getAndIncrement(0);
System.out.println("temp:" + temp + "; i:" + i); //2;3,2,3,4,5,6
temp = i.getAndAdd(0, 5);
System.out.println("temp:" + temp + "; i:" + i); //3;8,2,3,4,5,6
}
}
运行结果:
temp:1; i:{2,2,3,4,5,6}
temp:2; i:{3,2,3,4,5,6}
temp:3; i:{8,2,3,4,5,6}
4、原子更新引用类型
4.1 有哪些原子更新引用类型
在多线程环境下,如果需要原子更新整个类,需要使用原子更新引用类型:
AtomicReference
:原子更新引用类型AtomicStampedReference
:原子更新带有版本号的引用类型该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题
AtomicMarkableReference
:原子更新带有标记位的引用类型
这几个类的用法基本一致,这里以 AtomicReference 为例来介绍!
4.2 AtomicReference类常用方法
public final V get() //获取当前`AtomicReference`对象持有的引用对象。
public final void set(V newValue) //设置`AtomicReference`对象持有的引用对象为指定的新值。
public final boolean compareAndSet(V expect, V update) //比较当前引用对象是否等于`expect`,如果相等,则将引用对象更新为`update`,返回更新是否成功的布尔值。
public final V getAndSet(V newValue) //设置`AtomicReference`对象持有的引用对象为指定的新值,并返回设置前的旧值。
public final V getAndAccumulate(V x, BinaryOperator<V> accumulatorFunction) //使用提供的累加函数对当前引用对象和给定值进行累加操作,并返回累加前的值。
public final V accumulateAndGet(V x, BinaryOperator<V> accumulatorFunction) //使用提供的累加函数对当前引用对象和给定值进行累加操作,并更新引用对象为累加后的值,返回更新后的值。
public final V updateAndGet(UnaryOperator<V> updateFunction) //使用提供的更新函数对当前引用对象进行更新操作,并返回更新后的值。
public final V getAndUpdate(UnaryOperator<V> updateFunction) //使用提供的更新函数对当前引用对象进行更新操作,并返回更新前的旧值。
public final void lazySet(V newValue) //最终将`AtomicReference`对象持有的引用对象设置为指定的新值,但是不保证立即可见。
4.3 AtomicReference类使用示例
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) {
AtomicReference <Person> ar = new AtomicReference <Person> ();
Person person = new Person("Tom", 22);
ar.set(person);
Person updatePerson = new Person("Lily", 20);
ar.compareAndSet(person, updatePerson);
System.out.println(ar.get().getName());
System.out.println(ar.get().getAge());
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
上述代码首先创建了一个 Person
对象,然后把 Person
对象设置进 AtomicReference
对象中,然后调用 compareAndSet
方法,该方法就是通过 CAS 操作设置 ar。如果 ar 的值为 person
的话,则将其设置为 updatePerson
。实现原理与 AtomicInteger
类中的 compareAndSet
方法相同。
运行结果:
Lily
20
5、原子更新属性类型
5.1 有哪些原子更新属性类型
在多线程环境下,如果需要原子更新某个类里的某个字段时,需要用到原子更新属性类型:
AtomicIntegerFieldUpdater
:原子更新整形字段的更新器AtomicLongFieldUpdater
:原子更新长整形字段的更新器AtomicReferenceFieldUpdater
:原子更新引用类型里的字段的更新器
要想原子地更新对象的属性需要两步:
- 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性
- 待更新的对象的属性必须使用 public volatile 修饰符
这几个类的用法基本一致,这里以 AtomicIntegerFieldUpdater 为例来介绍!
5.2 AtomicIntegerFieldUpdater类常用方法
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<?> tclass, String fieldName) //静态方法,返回一个AtomicIntegerFieldUpdater对象,用于对指定类的指定整型字段进行原子更新操作。
public abstract boolean compareAndSet(Object obj, int expect, int update) //比较指定对象的整型字段的当前值是否等于expect,如果相等,则将整型字段更新为update,返回更新是否成功的布尔值。
public int getAndSet(Object obj, int newValue) //设置指定对象的整型字段为指定的新值,并返回设置前的旧值。
public int getAndIncrement(Object obj) //将指定对象的整型字段自增1,并返回自增前的值。
public int getAndDecrement(Object obj) //将指定对象的整型字段自减1,并返回自减前的值。
public int getAndAdd(Object obj, int delta) //将指定对象的整型字段增加指定的增量delta,并返回增加前的值。
public int incrementAndGet(Object obj) //将指定对象的整型字段自增1,并返回自增后的值。
public int decrementAndGet(Object obj) //将指定对象的整型字段自减1,并返回自减后的值。
public int addAndGet(Object obj, int delta) //将指定对象的整型字段增加指定的增量delta,并返回增加后的值。
5.3 AtomicIntegerFieldUpdater类使用示例
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22);
System.out.println(a.getAndIncrement(user));// 22
System.out.println(a.get(user));// 23
}
}
class User {
private String name;
public volatile int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
运行结果:
22
23
6、总结
6.1 什么是线程安全
简而言之,『线程安全』即『内存安全』,线程访问的内存决定了这个线程是否是安全的!
变量大致可以分为局部变量和共享变量,局部变量对于 JVM 来说是栈空间,大家都背过八股文,栈是线程私有的、非共享的,那自然也是内存安全的;
共享变量对于 JVM 来说一般是存在于堆上,堆上的东西是所有线程共享的,如果不加任何限制自然是不安全的!
6.2 什么是Atomic原子类
在Java中, Atomic 指一个操作是不可中断的,即使是多个线程环境中,某个线程的一个操作一旦开始,就不会被其他线程干扰!
所以,所谓Atomic原子类说简单点就是具有原子操作特征的类,其底层原理是利用 CAS
来确保对变量操作的原子性!
6.3 有哪些Atomic类及其作用(重要)
原子更新基本类型:使用原子的方式更新基本类型
AtomicInteger
:整型原子类AtomicLong
:长整型原子类AtomicBoolean
:布尔型原子类
原子更新数组类型:使用原子的方式更新数组里的某个元素
AtomicIntegerArray
:整型数组原子类AtomicLongArray
:长整型数组原子类AtomicReferenceArray
:引用类型数组原子类
原子更新引用类型:使用原子的方式更新引用类型
AtomicReference
:引用类型原子类AtomicMarkableReference
:原子更新带有标记的引用类型AtomicStampedReference
:原子更新带有版本号的引用类型该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
原子更新更新属性:使用原子的方式更新对象的某个字段
AtomicIntegerFieldUpdater
:原子更新整型字段的更新器AtomicLongFieldUpdater
:原子更新长整型字段的更新器AtomicReferenceFieldUpdater
:原子更新引用类型里的字段