對於簡單的計數和加減操作,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();
}
因爲進行原子操作的是類實例中的一個字段,所以操作的時候需要指明操作實例。