關於線程安全性問題的一個解決方案,我們之前對於之前所出現的線程安全性問題已經瞭解了兩個解決方案,第一個是synchronized,第二個是volatile。但是對於我們之前的數值序列生成器來講,使用volatile並不是一個合理的解決方案,因爲volatile只能夠保證讀和寫的多個線程之間的可見性,但是,並不能夠保證原子性,那麼,++(自增)並不是一個原子性操作,因此,volatile解決不了這個問題,那麼,我們本節再來學習另外一種解決線程安全性問題的方法,就是,原子類,原子類是保證就是提供原子性操作,那麼,這個是在JDK5的時候纔出現的,我們來看一下它如何使用,本節就對原子類的原理以及使用進行詳細的講解。
Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。因爲變量的類型有很多種,所以在Atomic包裏一共提供了13個類,屬於4種類型的原子更新方式,分別是
- 原子更新基本類型
- 原子更新數組
- 原子更新引用
- 原子更新屬性(字段)
Atomic包裏的類基本都是使用Unsafe實現的包裝類。
案列:之前有一篇關於自旋鎖的我們使用了這個原子性類。
TicketLock.java
public class TicketLock {
//排隊號
private AtomicInteger ticketNum = new AtomicInteger();
//服務號
private AtomicInteger serviceNum = new AtomicInteger(1);
//ThreadLocal
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public void lock() {
Thread currThread = Thread.currentThread();
int ticketNumCurrent = ticketNum.incrementAndGet();
threadLocal.set(ticketNumCurrent);
System.out.println("線程"+currThread.getName()+"開始嘗試獲取鎖");
while (ticketNumCurrent != serviceNum.get()){
// System.out.println("線程"+currThread.getName()+"正在嘗試獲取鎖");
}
System.out.println("線程"+currThread.getName()+"已經獲取鎖");
}
public void unlock() {
Thread currThread = Thread.currentThread();
int ticketNumCurrent = threadLocal.get();
System.out.println("線程"+currThread.getName()+"釋放鎖");
serviceNum.compareAndSet(ticketNumCurrent,ticketNumCurrent+1);
}
}
原子更新基本類型
使用原子的方式更新基本類型,Atomic包提供了以下3個類。
AtomicBoolean:原子更新布爾類型。
AtomicInteger:原子更新整型。
AtomicLong:原子更新長整型。
以上3個類提供的方法幾乎一模一樣,所以本節僅以AtomicInteger爲例進行講解,AtomicInteger的源碼如下:
這幾個類的用法一致,就以AtomicIntegerArray來總結下常用的方法:
- addAndGet(int i, int delta):以原子更新的方式將數組中索引爲i的元素與輸入值相加;
- getAndIncrement(int i):以原子更新的方式將數組中索引爲i的元素自增加1;
- compareAndSet(int i, int expect, int update):將數組中索引爲i的位置的元素進行更新
AtomicIntegerArray與AtomicInteger的方法基本一致,只不過在前者的方法中會多一個指定數組索引位i。
AtomicIntegerArray使用示例:
public class AtomicExample {
private static int[] value = new int[]{1, 2, 3};
private static AtomicIntegerArray integerArray = new AtomicIntegerArray(value);
public static void main(String[] args) {
//對數組中索引爲2的位置的元素加3
int result = integerArray.getAndAdd(2, 3);
System.out.println(integerArray.get(2));
System.out.println(result);
}
}
原子更新引用類型
如果需要原子更新引用類型變量的話,爲了保證線程安全,Atomic也提供了相關的類:
- AtomicReference<T>:原子更新引用類型;
- AtomicReferenceFieldUpdater:原子更新引用類型裏的字段;
- AtomicMarkableReference:原子更新帶有標記位的引用類型;
AtomicReference使用示例:
public class AtomicExample {
private static AtomicReference<User> reference = new AtomicReference<>();
public static void main(String[] args) {
User user1 = new User("a", 1);
reference.set(user1);
User user2 = new User("b",2);
User user = reference.getAndSet(user2);
System.out.println(user);
System.out.println(reference.get());
}
static class User {
private String userName;
private int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}
// User{userName='a', age=1}
// User{userName='b', age=2}
AtomicReferenceFieldUpdater使用示例:
public class AtomicExample {
public static void main(String[] args) {
AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Dog.class, String.class, "name");
Dog dog1 = new Dog();
updater.compareAndSet(dog1, dog1.name, "cat");
System.out.println(dog1.name);
}
}
class Dog {
volatile String name = "dog1";
}
原子更新字段類型
如果需要更新對象的某個字段,Atomic同樣也提供了相應的原子操作類:
- AtomicIntegeFieldUpdater:原子更新整型字段類;
- AtomicLongFieldUpdater:原子更新長整型字段類;
要想使用原子更新字段需要兩步操作:
原子更新字段類型類都是抽象類,只能通過靜態方法newUpdater來創建一個更新器,並且需要設置想要更新的類和屬性;
更新類的屬性必須使用public volatile進行修飾;
AtomicIntegerFieldUpdater使用示例:
public class AtomicExample {
private static AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
public static void main(String[] args) {
User user = new User("a", 1);
System.out.println(updater.getAndAdd(user, 5));
System.out.println(updater.addAndGet(user, 1));
System.out.println(updater.get(user));
}
static class User {
private String userName;
public volatile int age;
public User(String userName, int age) {
this.userName = userName;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", age=" + age +
'}';
}
}
}