本文將介紹什麼是閉鎖,在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();
}
}