Java高并发编程中CountDownLatch的详细介绍-刘宇
- 一、CountdownLatch简介
- 二、基本用法
- 1、构造方法
- 2、await()
- 3、await(long timeout, TimeUnit unit)
- 4、countDown()
- 三、使用场景
- 1、场景1
- 2、场景2
作者:刘宇
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。
一、CountdownLatch简介
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步。CountDownLatch能够使一个或多个线程在等待其他一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,则表示所有的线程都已经完成任务,然后在CountDownLatch上等待的线程就会继续执行下面的代码。
二、基本用法
1、构造方法
- 参数count为任务数量
public CountDownLatch(int count)
2、await()
- 调用await()方法的线程会被挂起,它会等待直到count值为0或者调用await()方法的线程被打断才继续执行
public void await() throws InterruptedException
3、await(long timeout, TimeUnit unit)
- 调用await()方法的线程会被挂起,等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
4、countDown()
- count值会减1
public void countDown()
三、使用场景
1、场景1
当我们从数据库查询到数据后,由于数据量庞大且要进行处理,那么我们就需要使用线程对数据进行处理,在处理完成后,我们要将处理完的数据进行保存操作。
package com.test.part3.countdown;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchExample {
private static Random random = new Random(System.currentTimeMillis());
//定义一个线程池,里面有两个线程
private static ExecutorService executorService = Executors.newFixedThreadPool(2);
//定义CountDownLatch,设置任务数为10
private static CountDownLatch countDownLatch = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
//模拟从数据库查询数据
int[] data = query();
for (int i=0;i<data.length;i++){
executorService.execute(new SimpleRunnable(data,i));
}
countDownLatch.await();
executorService.shutdown();
//模拟保存操作
System.out.println("进行保存等操作...");
}
public static int[] query(){
return new int[]{1,2,3,4,5,6,7,8,9,10};
}
static class SimpleRunnable implements Runnable {
private final int[] data;
private final int index;
public SimpleRunnable(int[] data, int index) {
this.data = data;
this.index = index;
}
@Override
public void run() {
try {
Thread.sleep(random.nextInt(2000));
} catch (InterruptedException e) {
e.printStackTrace();
}
int value = data[index];
//模拟处理数据,单数*10,双数*2
if (value % 2 == 0){
data[index] = value * 2;
}else {
data[index] = value * 10;
}
System.out.println(Thread.currentThread().getName()+"->完成角标:"+index+"的数据处理");
//任务数减1
countDownLatch.countDown();
}
}
}
输出结果:
pool-1-thread-1->完成角标:0的数据处理
pool-1-thread-2->完成角标:1的数据处理
pool-1-thread-1->完成角标:2的数据处理
pool-1-thread-2->完成角标:3的数据处理
pool-1-thread-2->完成角标:5的数据处理
pool-1-thread-1->完成角标:4的数据处理
pool-1-thread-2->完成角标:6的数据处理
pool-1-thread-2->完成角标:8的数据处理
pool-1-thread-2->完成角标:9的数据处理
pool-1-thread-1->完成角标:7的数据处理
进行保存等操作...
Process finished with exit code 0
2、场景2
假设我们需要将一边的数据库迁移到一个数据壶中,并且需要保证迁移的每张表数据量正确和每张表数据结构正确。那么就需要一些服务器做迁移工作,一些服务器做校验工作。
- 思路:
- 迁移服务器在获取一次数据肯定是一次迁移保存许多表的,且每张表有大量数据。保存数据后,将每张数据表的名称、抓取的数据量、抓取的数据表结构生成一个Event事件发送给校验服务器。
- 校验服务器拿到Event事件,对每张表的校验任务(校验数据量、校验数据结构等)分为许多子任务去校验,随后将校验信息保存的数据量和抓取的数据量是否一致、抓取的数据结构和保存的数据结构是否一致等保存到数据库中。那么问题来了,我们不能每校验一个子任务就去更新一次数据库,那无疑是一种非常消耗资源的事情,我们此时就可以利用CountDownLatch来完成,当一张表中的子任务全部校验完成后再保存校验信息。
- 一个Event事件里面是包含很多张表的,那么如何在全部校验完这个Event事件里的所有表后给迁移服务器一个响应呢,也可以使用CountDownLatch来完成。
- 代码:
Table.java
package com.test.part3.countdown;
/**
* 记录所处理table的名称,获取时拿到的原数据条数,数据库结构。
* target属性主要用于判断存入数据壶中的数据条数,数据结构是否与原先的一致。
*/
public class Table {
public String tableName;//抓取的表名称
public long sourceRecordCount;//抓取的数据量
public long targetRecoudCount = 0;//保存数据量是多少,由校验服务器获取
public String sourceColumnSchema;//抓取的数据表结构
public String targetColumnSchema = "";//保存数据表结构,由校验服务器获取
public Table(String tableName, long sourceRecordCount, String sourceColumnSchema) {
this.tableName = tableName;
this.sourceRecordCount = sourceRecordCount;
this.sourceColumnSchema = sourceColumnSchema;
}
@Override
public String toString() {
return "Table{" +
"tableName='" + tableName + '\'' +
", sourceRecordCount=" + sourceRecordCount +
", targetRecoudCount=" + targetRecoudCount +
", sourceColumnSchema='" + sourceColumnSchema + '\'' +
", targetColumnSchema='" + targetColumnSchema + '\'' +
'}';
}
}
·Event.java
package com.test.part3.countdown;
/**
* 事件类,在获取完数据存入数据壶中后生成事件,将所处理的所有table等信息传递给另一个组件进行数据校验
*/
public class Event {
private int id;
public Event(int id) {
this.id = id;
}
public int getId() {
return id;
}
}
RecordCountCheck.java
package com.test.part3.countdown;
import java.util.Random;
/**
* 数据量校验
* 将一次Event任务的一张表中的多个检查分为多个小的检查
*/
public class RecordCountCheck implements Runnable {
private Random random = new Random(System.currentTimeMillis());
private Table table;
private TaskBatch tasKBatch;
public RecordCountCheck(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.targetRecoudCount = table.sourceRecordCount;
//通知该张表的一个子任务校验完成
tasKBatch.done(table);
}
}
ColumnSchemaCheck.java
package com.test.part3.countdown;
import java.util.Random;
/**
* 数据库结构校验
* 将一次Event任务的一张表中的多个检查分为多个小的检查
*/
public class ColumnSchemaCheck implements Runnable {
private Random random = new Random(System.currentTimeMillis());
private Table table;
private TaskBatch tasKBatch;
public ColumnSchemaCheck(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;
//通知该张表的一个子任务校验完成
tasKBatch.done(table);
}
}
Watcher.java
package com.test.part3.countdown;
/**
* 定义观察任务是否完成接口
*/
interface Watcher {
void done(Table table);
}
TaskBatch.java
package com.test.part3.countdown;
import java.util.concurrent.CountDownLatch;
/**
* 每张表中子任务完成通知类,一张表中的子任务全部完成后进行保存该校验数据操作
*/
public class TaskBatch implements Watcher {
private CountDownLatch countDownLatch;//用于判断该张表中所有的校验子任务是否都完成
private TaskGroup taskGroup;//Event中表完成通知类
private volatile boolean isUpdate = false;
public TaskBatch(TaskGroup taskGroup, int size) {
this.countDownLatch = new CountDownLatch(size);
this.taskGroup = taskGroup;
}
@Override
public void done(Table table) {
//每完成一个子任务就减1
countDownLatch.countDown();
synchronized (this) {
//判断是否为0,且没有保存到数据库中
if (countDownLatch.getCount() == 0 && !isUpdate) {
isUpdate = true;
System.out.println("The table " + table.tableName + " finished work." + table.toString());
//通知该张表完成了所有子任务查询
taskGroup.done(table);
}
}
}
}
TaskGroup.java
package com.test.part3.countdown;
import java.util.concurrent.CountDownLatch;
/**
* 每张表完成通知,全部表完成后对Event事件做出响应
*/
public class TaskGroup implements Watcher {
private CountDownLatch countDownLatch;//用于判断该Event事件中所有的表是否都完成
private Event event;
private volatile boolean isFinish = false;
public TaskGroup(Event event,int size) {
this.countDownLatch = new CountDownLatch(size);
this.event = event;
}
@Override
public void done(Table table) {
//完成一张表就减1
countDownLatch.countDown();
synchronized (this) {
//当count为0时且没有做出响应时对该Event事件做出响应
if (countDownLatch.getCount() == 0 && !isFinish) {
isFinish = true;
System.out.println("Event "+event.getId()+" finished work");
}
}
}
}
CountDownLatchExample2.java
package com.test.part3.countdown;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchExample2 {
public static void main(String[] args) {
//模拟两台数据迁移服务器分别获取完数据存入数据壶后传递过来的Event事件,通知校验服务器进行校验工作
Event[] events = new Event[] {new Event(1),new Event(2)};
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (Event event : events){
//模拟获取到数据迁移服务器传递过来的event的数据
List<Table> tables = capture(event);
TaskGroup taskGroup = new TaskGroup(event,tables.size());
for (Table table : tables){
//一个表中需要检查两项,所以这边设置任务数2
TaskBatch tasKBatch = new TaskBatch(taskGroup,2);
//数据量校验子任务
RecordCountCheck recordCountCheck = new RecordCountCheck(table,tasKBatch);
//数据结构校验子任务
ColumnSchemaCheck columnSchemaCheck = new ColumnSchemaCheck(table,tasKBatch);
//提交任务
executorService.submit(recordCountCheck);
executorService.submit(columnSchemaCheck);
}
}
}
//模拟生成该Event事件对应的数据
public static List<Table> capture(Event event){
List<Table> list = new ArrayList<>();
for (int i=0;i<10;i++){
//封装数据迁移服务器迁移的数据表、该表数据量、表结构等信息
list.add(new Table("table-"+i+"::event-"+event.getId(),i*1000,"<table name='a'><column name='coll' type='varchar2' /></table>"));
}
return list;
}
}
输出结果:
The table table-2::event-1 finished work.Table{tableName='table-2::event-1', sourceRecordCount=2000, targetRecoudCount=2000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-1::event-1 finished work.Table{tableName='table-1::event-1', sourceRecordCount=1000, targetRecoudCount=1000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-0::event-1 finished work.Table{tableName='table-0::event-1', sourceRecordCount=0, targetRecoudCount=0, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-3::event-1 finished work.Table{tableName='table-3::event-1', sourceRecordCount=3000, targetRecoudCount=3000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-4::event-1 finished work.Table{tableName='table-4::event-1', sourceRecordCount=4000, targetRecoudCount=4000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-5::event-1 finished work.Table{tableName='table-5::event-1', sourceRecordCount=5000, targetRecoudCount=5000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-6::event-1 finished work.Table{tableName='table-6::event-1', sourceRecordCount=6000, targetRecoudCount=6000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-7::event-1 finished work.Table{tableName='table-7::event-1', sourceRecordCount=7000, targetRecoudCount=7000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-8::event-1 finished work.Table{tableName='table-8::event-1', sourceRecordCount=8000, targetRecoudCount=8000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-9::event-1 finished work.Table{tableName='table-9::event-1', sourceRecordCount=9000, targetRecoudCount=9000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
Event 1 finished work
The table table-0::event-2 finished work.Table{tableName='table-0::event-2', sourceRecordCount=0, targetRecoudCount=0, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-1::event-2 finished work.Table{tableName='table-1::event-2', sourceRecordCount=1000, targetRecoudCount=1000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-2::event-2 finished work.Table{tableName='table-2::event-2', sourceRecordCount=2000, targetRecoudCount=2000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-3::event-2 finished work.Table{tableName='table-3::event-2', sourceRecordCount=3000, targetRecoudCount=3000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-4::event-2 finished work.Table{tableName='table-4::event-2', sourceRecordCount=4000, targetRecoudCount=4000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-5::event-2 finished work.Table{tableName='table-5::event-2', sourceRecordCount=5000, targetRecoudCount=5000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-6::event-2 finished work.Table{tableName='table-6::event-2', sourceRecordCount=6000, targetRecoudCount=6000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-7::event-2 finished work.Table{tableName='table-7::event-2', sourceRecordCount=7000, targetRecoudCount=7000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-8::event-2 finished work.Table{tableName='table-8::event-2', sourceRecordCount=8000, targetRecoudCount=8000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
The table table-9::event-2 finished work.Table{tableName='table-9::event-2', sourceRecordCount=9000, targetRecoudCount=9000, sourceColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>', targetColumnSchema='<table name='a'><column name='coll' type='varchar2' /></table>'}
Event 2 finished work