一、i++ 是線程安全的嗎?
1、測試環境
- 使用jdk8
- 使用Executors創建線程池
- 線程池容量100執行1000次自增操作
2、代碼驗證
(1)全部代碼
public class UnsafeDemo {
private int cnt = 0;
public void UnSafeAdd() {
cnt++;
}
public int getCnt() {
return cnt;
}
public static void main(String[] args) {
int exePoolSize = 100;
int forSize = 1000;
UnsafeDemo unsafeDemo = new UnsafeDemo();
ExecutorService exe = Executors.newFixedThreadPool(exePoolSize);
CountDownLatch countDownLatch = new CountDownLatch(forSize);
for (int i = 0; i < forSize; i++) {
Runnable runnable = () -> {
unsafeDemo.UnSafeAdd();
countDownLatch.countDown();
};
exe.execute(runnable);
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
exe.shutdown();
System.out.println(unsafeDemo.getCnt());
}
}
(2)相關說明
- 線程池使用Executors.newFixedThreadPool(exePoolSize)創建,創建一個可複用的固定的線程池,exePoolSize表示線程池的容量大小。
- 使用CountDownLatch倒數器,保證各個線程執行完自增操作再執行輸出操作。
- 通過上面代碼,期望的輸出值是1000,但是多次測試,輸出結果都是小於1000.
(3)結論
由於內存不可見的原因,如果一個線程運算完後還沒刷到主內存,比如從0加到1,此時主內存仍爲0,而另一個線程讀取到0然後從0加到1,所以當他們執行完將數據刷到主內存,執行了兩次從0到1的操作,最終結果是1。
二、線程安全的自增操作
1、測試環境
- 使用jdk8
- 使用ThreadPoolExecutor創建線程池
- 使用AtomicInteger執行自增操作
2、代碼驗證
(1)全部代碼
public class SafeDemo {
private static int COREPOOLSIZE = 5;
private static int MAXIMUMPOOLSIZE = 200;
private static long KEEPALIVETIME = 0L;
private static int QUESIZE = 100;
// 拒絕策略
// ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常, 默認策略
// ThreadPoolExecutor.DiscardPolicy:丟棄任務不拋出異常。
// ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,然後重新嘗試執行任務(重複此過程)
// ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
private static ThreadPoolExecutor.CallerRunsPolicy rejectHandler = new ThreadPoolExecutor.CallerRunsPolicy();
// 儘量不使用Executors創建線程池,Executors有可能導致OOM發生
public static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE, KEEPALIVETIME, TimeUnit.SECONDS, new LinkedBlockingDeque<>(QUESIZE), Thread::new, rejectHandler);
public static void main(String[] args) throws InterruptedException {
AtomicInteger cnt = new AtomicInteger();
int forSize = 100000;
CountDownLatch countDownLatch = new CountDownLatch(forSize);
for (int i = 0; i < forSize; i++) {
Runnable runnable = () -> {
cnt.incrementAndGet();
countDownLatch.countDown();
};
poolExecutor.execute(runnable);
}
countDownLatch.await();
System.out.println(cnt.get());
}
}
(2)相關說明
- 此時使用ThreadPoolExecutor創建線程池,同樣在阿里巴巴代碼規範中也明確說明不能使用Executors創建線程池,防止在未知情況下OOM的發生
- 使用AtomicInteger,這是JUC包下atomic的其中一個代表類,使用CAS保證可見性。