线程池(五):线程池使用场景问题

线程池(五):线程池使用场景问题

1 线程池使用场景CountDownLatch、Future

1.1 CountDownLatch

CountDownLatch是Java并发包中的一个同步辅助类。它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。

原理

CountDownLatch内部维护了一个计数器,该计数器的初始值为需要等待完成的操作数量。当一个线程调用countDown()方法时,计数器会减1。而调用await()方法的线程会被阻塞,直到计数器的值变为0,此时所有等待的线程会被释放继续执行。

示例代码
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int numThreads = 5;
        CountDownLatch latch = new CountDownLatch(numThreads);

        for (int i = 0; i < numThreads; i++) {
            new Thread(() -> {
                try {
                    // 模拟线程执行任务
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + " 任务完成");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        // 主线程等待所有子线程完成任务
        latch.await();
        System.out.println("所有任务已完成,主线程继续执行");
    }
}

1.2 案例一(es数据批量导入)

需求分析

在将大量数据导入Elasticsearch(ES)时,为了提高导入效率,通常会采用多线程并发导入的方式。但是,在所有数据导入完成之前,可能需要等待所有线程执行完毕,才能进行后续的操作,比如数据校验、通知用户导入完成等。

实现步骤
  1. 初始化CountDownLatch:根据要并发导入的任务数量,初始化CountDownLatch的计数器值。例如,如果要分10个线程并发导入数据,计数器初始值就设为10。
  2. 创建线程执行导入任务:在每个线程中,编写数据导入ES的逻辑。在导入完成后,调用countDown()方法将计数器减1。
  3. 主线程等待:主线程调用await()方法,阻塞等待计数器变为0,即所有数据导入线程都完成任务。
示例代码
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class EsBulkImport {
    private static final RestHighLevelClient client = new RestHighLevelClient(); // 假设已正确初始化ES客户端
    private static final int BATCH_SIZE = 1000; // 每批导入的数据量
    private static final int THREAD_COUNT = 10; // 并发线程数

    public static void main(String[] args) throws InterruptedException {
        List<DataObject> dataList = new ArrayList<>(); // 假设已准备好要导入的数据
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);

        for (int i = 0; i < THREAD_COUNT; i++) {
            int startIndex = i * BATCH_SIZE;
            int endIndex = Math.min((i + 1) * BATCH_SIZE, dataList.size());
            List<DataObject> subList = dataList.subList(startIndex, endIndex);

            new Thread(() -> {
                BulkRequest bulkRequest = new BulkRequest();
                for (DataObject data : subList) {
                    bulkRequest.add(client.prepareIndex()
                           .setSource(data.toJson(), XContentType.JSON)
                           .request());
                }
                try {
                    BulkResponse bulkResponse = client.bulk(bulkRequest);
                    if (bulkResponse.hasFailures()) {
                        // 处理失败情况
                        System.err.println("导入失败: " + bulkResponse.buildFailureMessage());
                    } else {
                        System.out.println("部分数据导入成功");
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        latch.await();
        System.out.println("所有数据导入完成");
    }

    static class DataObject {
        // 假设数据对象有相关属性和方法
        public String toJson() {
            // 转换为JSON字符串的逻辑
            return "{}";
        }
    }
}

1.3 案例二(数据汇总)

需求分析

在一个系统中,可能需要从多个不同的数据源(如不同的数据库表、不同的API接口等)获取数据,然后将这些数据进行汇总分析。为了提高获取数据的效率,可以使用多线程并发从各个数据源获取数据,最后再进行汇总。

实现步骤
  1. 初始化CountDownLatch:根据数据源的数量,设置CountDownLatch的计数器值。比如有5个数据源,计数器就初始化为5。
  2. 创建线程获取数据:每个线程负责从一个数据源获取数据。获取完成后,调用countDown()方法。
  3. 汇总数据:主线程在调用await()方法等待所有数据获取线程完成后,进行数据汇总操作。
示例代码
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;

public class DataAggregation {
    public static void main(String[] args) throws InterruptedException {
        int numSources = 5;
        CountDownLatch latch = new CountDownLatch(numSources);
        List<Integer> dataList = new ArrayList<>();

        for (int i = 0; i < numSources; i++) {
            new Thread(() -> {
                try {
                    // 模拟从数据源获取数据
                    int data = getDataSourceData();
                    dataList.add(data);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    latch.countDown();
                }
            }).start();
        }

        latch.await();

        int sum = 0;
        for (int data : dataList) {
            sum += data;
        }
        System.out.println("汇总后的数据: " + sum);
    }

    private static int getDataSourceData() {
        // 模拟从数据源获取数据的逻辑,这里简单返回一个随机数
        return (int) (Math.random() * 100);
    }
}

1.4 案例三(异步调用)

需求分析

在一些业务场景中,需要发起多个异步调用(如调用多个远程服务接口),并且在所有异步调用完成后,再进行统一的处理(如合并结果、更新状态等)。使用CountDownLatch可以方便地实现这种等待所有异步操作完成的需求。

实现步骤
  1. 初始化CountDownLatch:根据异步调用的数量设置计数器值。例如有3个异步调用,计数器初始值设为3。
  2. 发起异步调用:在每个线程中发起异步调用(可以是通过网络请求远程服务等操作)。调用完成后,调用countDown()方法。
  3. 统一处理:主线程调用await()方法等待所有异步调用线程完成,然后进行后续的统一处理逻辑。
示例代码
import java.util.concurrent.CountDownLatch;

public class AsyncInvocation {
    public static void main(String[] args) throws InterruptedException {
        int numCalls = 3;
        CountDownLatch latch = new CountDownLatch(numCalls);

        for (int i = 0