CountDownLatch
CountDown 的意思是倒数,latch是门栓的意思,连起来就可以翻译为倒数的门栓。在我们Java体现的意思就是:在多线程的环境下,你可以规定有10个门栓(即10个线程在干活),当门栓数减到零(即每个线程完成自己的任务后会把门栓数减一,10个线程都完成后门栓数就减到了零),就可以统一执行接下来的任务。
接下来我们通过三个场景来了解 CountDownLatch 的使用:
- 第一个场景:我们先要去数据库查询一批重量级的数据,接着对这批数据进行处理,对这批数据处理完以后还需要进行二次处理。二次处理是建立在第一次处理基础之上的,所以需要等待第一次处理完以后二次处理才可以进行。
/**
* 在我们程序中一开始串行化处理后又遇见并行化处理了、等并行化处理完以后又要开始串行化处理、可以这么用
*/
public class CountDownLatchTest {
private static Random random = new Random(System.currentTimeMillis());
private static ExecutorService executor = Executors.newFixedThreadPool(2);
private static final CountDownLatch latch = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
// 1:查询数据 一批数据
// 2:数据处理
// 3:一批数据处理完 对数据进行二次处理
//1
int[] data = query();
//2
for (int i = 0; i < data.length; i++) {
executor.execute(new SimpleRunnable(data, i,latch));
}
//一直在等待,直到 latch.getCount() = 0 时才会释放,去执行下面的代码
latch.await();
//3
System.out.println(" all of worker finish done ");
executor.shutdown();
//等待一个小时、没到也会提前结束 此方法可行、但是这次使用 countDownLatch
//executor.awaitTermination(1, TimeUnit.HOURS);
}
static class SimpleRunnable implements Runnable {
private final int[] data;
private final int index;
private final CountDownLatch latch;
public 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;
}
System.out.println(Thread.currentThread().getName() + " finished ");
//每完成一次就调用一次减一的操作、
latch.countDown();
}
}
//相当于查询数据
private static int[] query() {
return new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
}
}
- 第二个场景:两个线程各自干活,而一个线程依赖另外一个线程处理好的数据才能接着干活。
public class CountDownLatchTest {
private static final CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
new Thread() {
@Override
public void run() {
System.out.println(" --开始干活-----");
try {
Thread.sleep(100);
System.out.println(" 干完等待别的数据进来-------");
latch.await();
System.out.println("别人数据进来了,我要开始干活了=======");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(){
@Override
public void run() {
System.out.println("开始帮别人处理数据---");
try {
Thread.sleep(10);
System.out.println("帮别人处理的数据处理好了-------");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
Thread.currentThread().join();
}
}
- 第三个场景:假设有一个系统会不断的爬取数据存储到多个数据库,但是数据进行校验后才能使用,现在需要对表中数据量的个数以及数据的标准性进行校验。这时我们需要监控某个表是否是已经校验完成,还需要监控某个数据的数据是否已经处理完毕。
public class CountDownLatchTest {
private static Random random = new Random(System.currentTimeMillis());
//事件、或者数据库
static class Event {
int id;
public Event(int id) {
this.id = id;
}
}
interface Watcher {
void done(Table table);
}
//检测表里面数据是否完成
static class TaskBatch implements Watcher {
private CountDownLatch countDownLatch;
private TaskGroup taskGroup;
public TaskBatch(TaskGroup taskGroup, int size) {
this.taskGroup = taskGroup;
this.countDownLatch = new CountDownLatch(size);
}
@Override
public void done(Table table) {
countDownLatch.countDown();
if (countDownLatch.getCount() == 0) {
System.out.println(" The table " + table.tableName + " finished work ,[" + table + "]");
taskGroup.done(table);
}
}
}
//检测数据库数据是否已经处理完毕
static class TaskGroup implements Watcher {
private CountDownLatch countDownLatch;
private Event event;
public TaskGroup(int size, Event event) {
this.countDownLatch = new CountDownLatch(size);
this.event = event;
}
@Override
public void done(Table table) {
countDownLatch.countDown();
if (countDownLatch.getCount() == 0) {
System.out.println(" ==== All of table done in event : " + event.id);
}
}
}
// 数据表
static class Table {
String tableName;
long sourceRecordCount = 0;
long targetCount;
String sourceColumnSchema = "<table name = 'a'><column name ='coll' type = 'varchar2'/></table>";
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 +
", sourceColumnSchema='" + sourceColumnSchema + '\'' +
", 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;
}
public static void main(String[] args) {
//模拟有两个数据库里的数据需要处理
Event[] events = {new Event(1), new Event(2)};
ExecutorService service = Executors.newFixedThreadPool(5);
for (Event event : events) {
List<Table> tables = capture(event);
//检测某个数据库里面的数据是否已经处理完毕
TaskGroup taskGroup = new TaskGroup(tables.size(), event);
//接着对table进行校验
for (Table table : tables) {
//检测某个表里面的数据是否已经处理完毕
TaskBatch taskBatch = new TaskBatch(taskGroup, 2);
TrustSourceRecordCount recordCountRunnable = new TrustSourceRecordCount(table, taskBatch);
TrustSourceColumns columnsRunnable = new TrustSourceColumns(table, taskBatch);
service.submit(recordCountRunnable);
service.submit(columnsRunnable);
}
}
}
//校验数据量是否一致
static class TrustSourceRecordCount implements Runnable {
private final Table table;
private final TaskBatch taskBatch;
public 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 + " target record capture done and update ! ");
taskBatch.done(table);
}
}
//校验数据的准确性
static class TrustSourceColumns implements Runnable {
private final Table table;
private final TaskBatch taskBatch;
public 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.sourceColumnSchema;
// System.out.println(" The table " + table.tableName + " target column capture done and update ! ");
taskBatch.done(table);
}
}
}