对于简单的计数和加减操作,lock和synchronized的锁粒度太大,容易导致高并发时效率问题。J.U.C包中提供了一个更加高效的解决方案——Atomic类。它的底层通过CAS操作来实现并发安全性。
Atomic类里提供了4种操作对象,下面分别介绍。
一、 基本原子类型
AtomicBoolean
AtomicInteger
AtomicLong
以AtomicInteger为例,他们常用的方法
getAndIncrement |
先返回原值,再+1 |
incrementAndGet | 先+1,再返回+1后的值 |
getAndDecrement |
先返回原值,再-1 |
decrementAndGet | 先-1,再返回-1后的值 |
addAndget(int value) | 先+value,然后返回计算的值 |
getAndAdd(int value) |
先返回原值,然后将原值+value |
compareAndSet(e, n) |
原值若是期望值e就设置为新值new,返回true; 否则,就不更改,返回false |
accumulateAndGet(x, IntBinaryOperator function) |
使用当前值与 x 进行function计算,并将计算结果返回 |
getAndaccumulate(x, IntBinaryOperator function) | 先返回当前值,然后将当前值与x进行function计算,结果存为新值 |
前面的都比较好理解,这里就举最后两个的例子:
AtomicInteger a = new AtomicInteger(2);
System.out.println(a.getAndAccumulate(5, Math::max)); //2 ,先返回后计算max(a, 5)
System.out.println(a); //5 ,max(a,5)之后的值存入了a
System.out.println(a.accumulateAndGet(10, Math::max)); //10
System.out.println(a); //10
二、原子数组
原子数组就是可以声明原子变量的数组,然后对这个数组的各个变量进行原子操作。
int[] data = {1,2,3,4};
AtomicIntegerArray array = new AtomicIntegerArray(data);
System.out.println(array.getAndSet(1, 10)); //对[1]的数设置为10
System.out.println(array.get(1)); //10
它的方法与基本原子类型基本相同,只是操作的时候第一个操作数是数组的下标。
三、原子抽象类型
我们可以对一个类的对象进行原子操作,方法就是用到AtomicReference<>
public class User {
public volatile int age;
public User(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(){
AtomicReference<User> user = new AtomicReference<>(); //声明User为原子抽象类型
System.out.println(user); //null
User user1 = new User(10);
System.out.println(user1); //User@74a14482
user.getAndSet(user1); //对user对象进行原子操作
System.out.println(user); //User@74a14482,与user1相同
}
需要说明的是, User类为原子抽象类型,是指我们可以对User类的对象进行原子操作,而不是我们可以对User类中的一个属性变量可以原子操作(如 int age, 是不能原子操作的)。能对其进行操作的是下一个——原子字段。
四、原子(抽象类型)字段
原子字段是指对一个类中的某一个字段进行原子操作。
public static void main(){
User user = new User(15);
//将User.class 中的 "age"字段声明为原子字段
AtomicIntegerFieldUpdater<User> data = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
data.set(user, 25); //设置时需要指明对象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(data.getAndIncrement(user)); //25
System.out.println(data.get(user)); //26
}
}).start();
}
因为进行原子操作的是类实例中的一个字段,所以操作的时候需要指明操作实例。