問題背景描述
啓用線程池批量執行一些定時任務,每次需要這些任務都執行完,再開始下一次的任務批量執行。
如下圖:
如上圖的業務描述,如果不對批量的線程任務做原子性限制,假如第一次執行定時任務,某條線程任務執行的比較慢,8分鐘了還沒執行完。第二次執行定時任務時,由於該條任務數據庫數據狀態沒有改變,還會被讀取出來,導致重複執行。所以,需要對每次定時任務的這些批量任務加原子性限制,只有當前這次的批量任務都做完了,才能開始下一次批量任務。
解決方案
使用CountDownLatch工具類,可以實現“批量線程”同步。
CountDownLatch類可以通過await()方法,讓當前線程處於一直阻塞等待狀態,直到通過countDown() 方法將線程的計數器減爲0。在初始化CountDownLatch時,指定線程計數總數。
public void executor() {
System.out.println("---------任務調度開始-------------");
CountDownLatch latch = new CountDownLatch(3);
for (TSysTask taskInfo : taskList) {
AbstractSysTask task = new MyTask();
sysTaskThreadPool.execute(task);
}
System.out.println("---------任務調度中-------------");
try {
latch.await();
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
System.out.println("---------任務調度結束-------------");
}
分析:
(1)只有通過調用 latch.await() 方法纔會實現“批量線程”同步;不調用這個方法,是沒有任何效果的;
也就是說,即便初始化時指定了計數器個數,如果不調用 latch.await() 方法,怎麼調用countDown方法都沒用的。
(2)調用 latch.await() 方法,CountDownLatch工具類會立刻將當前方法 executor 鎖定給當前線程,此時別的其他線程是無法執行executor方法的,得排隊等着。直到通過countDown方法將同步計數器減爲0.
(3)調用 latch.await() 方法,當前線程會立刻處於類似sleep的狀態,直到同步計數器減爲0。也就是說, latch.await() 方法後面的代碼不會被執行,直到同步計數器減爲0。
(4)CountDownLatch 機制不是用來保護共享資源或者臨界區。它是用來同步一個或者多個線程。它只能使用一次。也就是說,一旦CountDownLatch的計數器到達0,任何對它的方法的調用都是無效的。如果你想再次同步,你必須創建新的對象。