Java高并发编程中CountDownLatch的详细介绍-刘宇

作者:刘宇
CSDN博客地址:https://blog.csdn.net/liuyu973971883
有部分资料参考,如有侵权,请联系删除。如有不正确的地方,烦请指正,谢谢。

一、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

假设我们需要将一边的数据库迁移到一个数据壶中,并且需要保证迁移的每张表数据量正确和每张表数据结构正确。那么就需要一些服务器做迁移工作,一些服务器做校验工作。

  • 思路:
  1. 迁移服务器在获取一次数据肯定是一次迁移保存许多表的,且每张表有大量数据。保存数据后,将每张数据表的名称、抓取的数据量、抓取的数据表结构生成一个Event事件发送给校验服务器。
  2. 校验服务器拿到Event事件,对每张表的校验任务(校验数据量、校验数据结构等)分为许多子任务去校验,随后将校验信息保存的数据量和抓取的数据量是否一致、抓取的数据结构和保存的数据结构是否一致等保存到数据库中。那么问题来了,我们不能每校验一个子任务就去更新一次数据库,那无疑是一种非常消耗资源的事情,我们此时就可以利用CountDownLatch来完成,当一张表中的子任务全部校验完成后再保存校验信息。
  3. 一个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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值