線程安全性是指:
當多個線程訪問某個類時,不管運行時環境採用何種調度方式或者這些進程將如何交替執行,並且在主調代碼中不需要任何額外的同步或協調,這個類都能表現出正確的行爲,則稱這個類時線程安全的。
線程安全性的三個方面:
- 原子性:提供了互斥訪問,同一時刻只能有一個線程來對它進行操作
- 可見性:一個線程對主內存的修改可以及時的被其他線程觀察到
- 有序性:一個線程觀察其他線程中的指令執行順序,由於指令重排序的存在,該觀察結果一般雜亂無序
代碼基礎
1.CountDownLatch
countDownLatch字面意思是計數器向下減的閉鎖。計數器的初始值爲線程的數量。每當一個線程完成了自已的任務,調用countDown()方法後,計數器的值就會減1。當計數器的值爲0的時候,表示所有的線程完成了任務。在閉鎖上等待的線程就可以恢復執行。 ,如下圖所示
計數器的初始值爲3,Ta線程執行await()方法之後,就進入了等待狀態,只有當T1,T2,T3都執行countDown()方法後,計數器的值爲0,Ta線程才能繼續執行。
2.Semaphore
Semaphore是一種計數信號量,用於控制同一時間的請求併發量。
如圖所示,對於一個十字路口,同一時間只允許兩輛車通過同一個點,當兩輛車中的一個離開時,就可以允許等待的車中的一輛通過。
3.代碼示例
public class Concurrency {
//請求總數
private static int clientTotal = 1000;
//同時允許執行的線程總數
private static int threadTotal = 20;
private static int count = 1;
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0;i<clientTotal;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
count++;
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count="+count);
}
上述代碼執行多次,其輸出結果都不相同,說明不是線程安全的。線程的執行體只有一句話,count++。接下來根據jmm(Java memory model java內存模型)模型,來分析一下爲什麼count++操作不是線程安全的。
jmm模型規範了一個線程如何和何時看到一個共享變量被其他線程修改的值,以及在必須時如何同步的訪問共享變量。
對於count++操作線程不安全根據上圖就很好解釋了。假設線程A從主內存獲取到count的拷貝保存在A的本地內存中,值爲1,線程B也從主內存獲取count的拷貝保存到本地內存中,值爲1。A和B同時執行+1操作,count變爲了2,寫入到主內存中。此時count的值爲2,但實際應該是3。
4.AtomicXXX
上述count++操作線程不安全,爲了變爲線程安全,我們使用java.util.concurrent.Atomic包下的AtomicInteger來替換int。 代碼示例:
public class AtomicIntegerConcurrency {
//請求總數
private static int clientTotal = 1000;
//同時允許執行的線程總數
private static int threadTotal = 20;
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0;i<clientTotal;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
count.addAndGet(1);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("count="+count.get());
}
}
其輸出結果始終是1000。 接下來看一下AutomicXXX爲什麼是線程安全的。
//AtomicInteger
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
//Unsafe
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
內部實現主要爲Unsafe.compareAndSwapInt,簡稱cas。 compareAndSwapInt使用了native關鍵字,說明不是java寫的方法。核心是通過wahile循環,判斷當前的值與底層的值相同,才執行操作。
AtomicBoolean的使用。下面代碼可以用於某些只要求執行一次的情景
public class AtomicBooleanConcurrency {
//請求總數
private static int clientTotal = 1000;
//同時允許執行的線程總數
private static int threadTotal = 20;
private static AtomicBoolean isExecuted = new AtomicBoolean(false);
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i=0;i<clientTotal;i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
if (isExecuted.compareAndSet(false,true)){
//雖然有1000個線程執行,但是這句話只輸出一次,因爲AtomicBoolean的compareAndSet是原子操作,從false變爲true只會執行一次
System.out.println("execute success");
}
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("isExecuted="+isExecuted.get());
}