【詳解】JUC之CountDownLatch

一、引入

  • 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);
        }

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章