一、引入
- countDownLatch這個類使一個線程等待其他線程各自執行完畢後再執行。
- 是通過一個
計數器
來實現的,計數器的初始值是線程的數量或者任務的數量。 - 每當一個線程執行完畢後,計數器的值就-1,當計數器的值爲0時,表示所有線程都執行完畢,然後在閉鎖上等待的線程就可以恢復工作了。
- CountDownLatch的方便之處在於,你可以在一個線程中使用,也可以在多個線程上使用,一切只依據狀態值,這樣便不會受限於任何的場景。
二、分析
在java5提供的併發包下,有一個AbstractQueuedSynchronizer抽象類,也叫AQS,此類根據大部分併發共性作了一些抽象,便於開發者實現如排他鎖,共享鎖,條件等待等更高級的業務功能。
它通過使用CAS和隊列模型,出色的完成了抽象任務
- 一開始,我們創建了一個CountDownLatch實例
- 此時,AQS中,狀態值state=2,對於 CountDownLatch 來說,state=2表示所有調用await方法的線程都應該阻塞,等到同一個latch被調用兩次countDown後才能喚醒沉睡的線程。接着線程3和線程4執行了 await方法,這會的狀態圖如下:
- 上面的通知狀態是節點的屬性,表示該節點出隊後,必須喚醒其後續的節點線程。
- 當線程1和線程2分別執行完latch.countDown方法後,會把state值置爲0,
- 此時,
通過CAS成功置爲0的那個線程將會同時承擔起喚醒隊列中第一個節點線程的任務
,從上圖可以看出,第一個節點即爲線程3,當線程3恢復執行之後,其發現狀態值爲通知狀態,所以會喚醒後續節點,即線程4節點,然後線程3繼續做自己的事情,到這裏,線程3和線程4都已經被喚醒,CountDownLatch功成身退。
三、使用場景一
需求
- 可能剛從數據庫讀取了一批數據
- 利用併發處理這批數據
- 當所有的數據處理完成後,再去執行後面的操作
解決方案
- 第一種:可以利用 join 的方法,但是在線程池中,比較麻煩
- 第二種:利用線程池的awaitTermination,阻塞一段時間
- 第三種:利用CountDownLatch,每當任務完成一個,就計數器減一
public class CountDownLatchExample {
private static final Random RANDOM = new Random(System.currentTimeMillis());
private static ExecutorService executor = Executors.newFixedThreadPool(2);//線程池
private static CountDownLatch latch ;
public static void main(String[] args) throws InterruptedException {
int[] data = query();//模擬從數據庫查詢的一批數據
latch = new CountDownLatch(data.length);
//讓線程併發的處理數據
for (int i = 0; i < data.length; i++) {
executor.execute(new SimpleRunnable(data,i, latch));
}
latch.await();
executor.shutdown();
// executor.awaitTermination(1, TimeUnit.HOURS);//利用線程池的等待機制,會阻塞住
System.out.println("all of finish done!!");
//等待全部線程處理完
}
static class SimpleRunnable implements Runnable{
private final int [] data;
private final int index;
private final CountDownLatch latch;
SimpleRunnable(int[] data, int index, CountDownLatch latch) {
this.data = data;
this.index = index;
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
int value = data[index];
//數據處理邏輯
if (value%2==0){
data[index] = value*2;
}else {
data[index] = value*10;
}
latch.countDown();
System.out.println(Thread.currentThread().getName() + " is finished.");
}
}
private static int[] query(){
return new int[]{1,2,3,4,5};
}
}
結果
pool-1-thread-2 is finished.
pool-1-thread-1 is finished.
pool-1-thread-2 is finished.
pool-1-thread-1 is finished.
pool-1-thread-2 is finished.
all of finish done!!
四、使用場景二
需求
- 多個線程協同工作
- 嘗試多個線程需要等待其他線程的工作
- 被喚醒後繼續執行其他操作
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " Do some initial working.");
try {
Thread.sleep(1000);
latch.await();
System.out.println(Thread.currentThread().getName() + " Do other working.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " Do some initial working.");
try {
Thread.sleep(1000);
latch.await();
System.out.println(Thread.currentThread().getName() + " Do other working.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
System.out.println("asyn prepare for some data.");
try {
Thread.sleep(2000);
System.out.println("Data prepare for done.");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
latch.countDown();
}
}).start();
}
}
五、API使用
構造方法只有一個
CountDownLatch(int count)
:構造一個以給定計數
實例方法
-
public void await()
- 當前線程等到鎖存器計數到零
- 可以被打斷
-
public boolean await(long timeout,TimeUnit unit)
- 等待一段時間
- timeout - 等待的最長時間 ,unit - timeout參數的時間單位
- 如果指定的等待時間過去,則返回值false
- 如果計數達到零,則方法返回值爲true
-
public void countDown()
- 減少鎖存器的計數,如果計數達到零,釋放所有等待的線程。
-
public long getCount()
- 返回當前計數
六、給離散的平行任務增加邏輯層次關係
需求
- 併發的從很多的數據庫讀取大量數據
- 在讀取數據的過程中,某個表可能會出現:
數據丟失、數據精度丟失、數據大小不匹配
- 需要進行對數據的各個情況進行檢測,這個檢測是併發的完成的
- 所以需要控制如果一個表所有的情況檢測完成,再進行後續的操作
解決
- 利用
CountDownLatch
的計數器 - 每當一個檢測完成,
計數器減一
- 如果計數爲0,執行後面操作
public class CountDownLatchExample {
private static final Random RANDOM= new Random();
public static void main(String[] args) throws Exception {
Event [] events = {new Event(1),new Event(2)};
ExecutorService service = Executors.newFixedThreadPool(5);
for (Event event : events) {
List<Table> tables = capture(event);
for (Table table : tables) {
TaskBatch taskBatch = new TaskBatch(2);
TrustSourceColumns sourceColumns = new TrustSourceColumns(table, taskBatch);
TrustSourceRecordCount recordCount = new TrustSourceRecordCount(table, taskBatch);
service.submit(sourceColumns);
service.submit(recordCount);
}
}
}
static class Event{
private int id;
Event(int id) {
this.id = id;
}
}
interface Watcher{
void done(Table table);
}
static class TaskBatch implements Watcher{
private final CountDownLatch latch;
TaskBatch(int size) {
this.latch = new CountDownLatch(size);
}
@Override
public void done(Table table) {
latch.countDown();
if (latch.getCount() == 0){
System.out.println("The table " + table.tableName + " finished work , " + table.toString());
}
}
}
static class Table{
String tableName;
long sourceRecordCount;
long targetCount;
String columnSchema = " tableName = a | column1Type = varchar";
String targetColumnSchema = "";
public Table(String tableName,long sourceRecordCount) {
this.tableName = tableName;
this.sourceRecordCount = sourceRecordCount;
}
@Override
public String toString() {
return "Table{" +
"tableName='" + tableName + '\'' +
", sourceRecordCount=" + sourceRecordCount +
", targetCount=" + targetCount +
", columnSchema='" + columnSchema + '\'' +
", targetColumnSchema='" + targetColumnSchema + '\'' +
'}';
}
}
private static List<Table> capture(Event event){
List<Table> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Table("table-"+event.id + "-" +i,i*1000));
}
return list;
}
static class TrustSourceRecordCount implements Runnable{
private final Table table;
private final TaskBatch taskBatch;
TrustSourceRecordCount(Table table, TaskBatch taskBatch) {
this.table = table;
this.taskBatch = taskBatch;
}
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
table.targetCount = table.sourceRecordCount;
// System.out.println("The table : " + table.tableName + " record count capture done and update.");
taskBatch.done(table);
}
}
static class TrustSourceColumns implements Runnable{
private final Table table;
private final TaskBatch taskBatch;
TrustSourceColumns(Table table, TaskBatch taskBatch) {
this.table = table;
this.taskBatch = taskBatch;
}
@Override
public void run() {
try {
Thread.sleep(RANDOM.nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
table.targetColumnSchema = table.columnSchema;
// System.out.println("The table : " + table.tableName + " target columns capture done and update.");
taskBatch.done(table);
}
}
}