【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