Java併發程式設計(8)-使用閉鎖測試併發時執行緒安全性
本文將介紹什麼是閉鎖,在java中的閉鎖實現:CountDownLatch類及其常用方法等,最後給出了一個使用閉鎖模擬執行緒併發的demo,用以簡單地測試任務是否為執行緒安全。
一、什麼是閉鎖
閉鎖(Latch)是在併發程式設計中常被提及的概念。閉鎖是一種執行緒控制物件,它能讓所有的執行緒在某個狀態時終止工作並等待,直到閉鎖“開門”時,所有的執行緒在這一刻會幾乎同時執行工作,製造出一個併發的環境。
二、CountDownLatch類介紹
2.1、什麼是CountDownLatch
CountDownLatch,顧名思義,可以理解為計數(count)、減少(down)、閉鎖(Latch),即通過計數減少的方式來達到阻礙執行緒執行任務的一種閉鎖,這個類位於java.util.concurent併發包下,是java中閉鎖的最優實現。
2.2、構造方法
CountDownLatch(int count)
構造器中計數值(count)就是閉鎖需要等待的執行緒數量,這個值只能被設定一次。
2.3、主要方法
-
void await(): 使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷。
-
boolean await(long timeout, TimeUnit unit): 使當前執行緒在鎖存器倒計數至零之前一直等待,除非執行緒被中斷或超出了指定的等待時間。
-
void countDown(): 遞減鎖存器的計數,如果計數到達零,則釋放所有等待的執行緒。
-
long getCount(): 返回當前計數。
-
String toString(): 返回標識此鎖存器及其狀態的字串。
三、使用閉鎖完成併發測試
使用閉鎖完成併發測試的基本思路是,定義一個startLatch閉鎖,並且它的執行緒等待值設定為1;之後新建的每一個執行緒都需要在執行任務前都在這個startLatch下等待,等所有執行緒都已集合完畢後,釋放startLatch,讓所有的執行緒都執行任務。
- 測試非執行緒安全的程式:
private int count_unsafe = 0;
class MyTask_Unsafe implements Runnable {
@Override
public void run() {
count_unsafe++;
System.out.println(Thread.currentThread().getName() + "讀數為" + count_unsafe);
}
}
@Test
public void testConcurrent() {
//新建閉鎖
CountDownLatch startLatch = new CountDownLatch(1);
//模擬100個執行緒去併發執行任務
for (int i = 0; i < 100; i++) {
new Thread() {
public void run() {
try {
//執行緒工作到此處即停止
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//在閉鎖允許後,呼叫任務
new MyTask_Unsafe().run();
}
}.start();
}
//100次迴圈後,100個執行緒已經建立並且在等待中,可以統一開始執行任務
System.out.println("所有執行緒集合完畢,等待執行任務...");
//開始執行任務
startLatch.countDown();
}
}
執行結果為:
可以發現,有3個執行緒輸出的數字都為7,原因在第一篇文章中已經講過,count_unsafe++這個操作並不是原子的,而是存在讀-改-寫
三個過程,所以執行緒不安全,下面我們測試一下執行緒安全的任務:
private AtomicInteger count = new AtomicInteger(0);
class MyTask_Safe implements Runnable {
//執行緒安全類
@Override
public void run() {
count.incrementAndGet();
System.out.println(Thread.currentThread().getName() + "讀數為" + count);
}
}
@Test
public void testConcurrent() {
//新建閉鎖
CountDownLatch startLatch = new CountDownLatch(1);
//模擬100個執行緒去併發執行任務
for (int i = 0; i < 100; i++) {
new Thread() {
public void run() {
try {
//執行緒工作到此處即停止
startLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//在閉鎖允許後,呼叫任務
new MyTask_Safe().run();
}
}.start();
}
//100次迴圈後,100個執行緒已經建立並且在等待中,可以統一開始執行任務
System.out.println("所有執行緒集合完畢,等待執行任務...");
//開始執行任務
startLatch.countDown();
}
}