转载

Java并发利器:CountDownLatch深度解析与实战应用

Java并发利器:CountDownLatch深度解析与实战应用
多线程编程中,让主线程等待所有子任务完成是个常见需求。CountDownLatch就像一个倒计时器,当所有任务完成后,主线程才继续执行。本文将通过简单易懂的方式,带你掌握这个强大的并发工具。

一、CountDownLatch是什么?

  1. 基本概念
    CountDownLatch就是一个"倒计数门闩":

倒计数:从指定数字开始递减到0
门闩:当计数为0时,门闩打开,等待的线程继续执行
一次性:用完即弃,不能重置

创建CountDownLatch 3

启动3个任务

任务1完成 countDown

任务2完成 countDown

任务3完成 countDown

计数器=0?

主线程继续执行

继续等待

  1. 基本用法
    public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

     // 创建计数器,初始值为3
     CountDownLatch latch = new CountDownLatch(3);
    
     // 启动3个任务
     for (int i = 0; i < 3; i++) {
         final int taskId = i;
         new Thread(() -> {
             System.out.println("任务" + taskId + "开始执行");
             try {
                 Thread.sleep(2000); // 模拟任务执行
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("任务" + taskId + "执行完成");
             latch.countDown(); // 计数器减1
         }).start();
     }
    
     System.out.println("主线程等待所有任务完成...");
     latch.await(); // 等待计数器变为0
     System.out.println("所有任务完成,主线程继续执行");
    

    }
    }
    运行结果:

主线程等待所有任务完成...
任务0开始执行
任务1开始执行
任务2开始执行
任务0执行完成
任务1执行完成
任务2执行完成
所有任务完成,主线程继续执行
二、核心API介绍
CountDownLatch只有4个关键方法:

public class CountDownLatchAPI {
public void demonstrateAPI() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);

    // 1. countDown() - 计数器减1
    latch.countDown();

    // 2. await() - 等待计数器变为0
    latch.await();

    // 3. await(时间, 单位) - 超时等待
    boolean finished = latch.await(5, TimeUnit.SECONDS);

    // 4. getCount() - 获取当前计数值
    long count = latch.getCount();
    System.out.println("剩余计数: " + count);
}

}
三、经典应用场景
场景1:等待多个任务完成
最常用的场景,主线程等待所有子任务完成:

public class WaitMultipleTasksDemo {

// 模拟订单处理:需要等待库存检查、用户验证、支付验证都完成
public void processOrder(String orderId) throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(3);

    // 库存检查
    new Thread(() -> {
        try {
            System.out.println("开始库存检查...");
            Thread.sleep(1000);
            System.out.println("库存检查完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown();
        }
    }).start();

    // 用户验证
    new Thread(() -> {
        try {
            System.out.println("开始用户验证...");
            Thread.sleep(1500);
            System.out.println("用户验证完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown();
        }
    }).start();

    // 支付验证
    new Thread(() -> {
        try {
            System.out.println("开始支付验证...");
            Thread.sleep(800);
            System.out.println("支付验证完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown();
        }
    }).start();

    System.out.println("等待所有验证完成...");
    latch.await();
    System.out.println("订单处理完成: " + orderId);
}

}
场景2:控制并发启动
让多个线程同时开始执行:

public class ConcurrentStartDemo {

// 模拟赛跑:所有选手同时起跑
public void startRace() throws InterruptedException {
    int runnerCount = 5;
    CountDownLatch startGun = new CountDownLatch(1); // 发令枪
    CountDownLatch finish = new CountDownLatch(runnerCount); // 终点线

    // 创建选手
    for (int i = 0; i < runnerCount; i++) {
        final int runnerId = i;
        new Thread(() -> {
            try {
                System.out.println("选手" + runnerId + "准备就绪");
                startGun.await(); // 等待发令枪

                // 开始跑步
                System.out.println("选手" + runnerId + "开始跑步");
                Thread.sleep(new Random().nextInt(3000)); // 模拟跑步时间
                System.out.println("选手" + runnerId + "到达终点");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                finish.countDown();
            }
        }).start();
    }

    Thread.sleep(2000); // 等待选手准备
    System.out.println("预备...开始!");
    startGun.countDown(); // 发令

    finish.await(); // 等待所有选手完成
    System.out.println("比赛结束!");
}

}
场景3:分段计算
将大任务拆分成小任务并行计算:

public class ParallelCalculationDemo {

// 并行计算数组的和
public long calculateSum(int[] array) throws InterruptedException {
    int threadCount = 4;
    CountDownLatch latch = new CountDownLatch(threadCount);
    AtomicLong totalSum = new AtomicLong(0);

    int chunkSize = array.length / threadCount;

    for (int i = 0; i < threadCount; i++) {
        final int start = i * chunkSize;
        final int end = (i == threadCount - 1) ? array.length : (i + 1) * chunkSize;

        new Thread(() -> {
            long partialSum = 0;
            for (int j = start; j < end; j++) {
                partialSum += array[j];
            }
            totalSum.addAndGet(partialSum);
            System.out.println("线程计算范围[" + start + "," + end + "),结果:" + partialSum);
            latch.countDown();
        }).start();
    }

    latch.await();
    return totalSum.get();
}

public static void main(String[] args) throws InterruptedException {
    int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    ParallelCalculationDemo demo = new ParallelCalculationDemo();
    long result = demo.calculateSum(array);
    System.out.println("总和:" + result);
}

}
四、使用注意事项

  1. 异常处理要点
    核心原则:无论是否异常,都要调用countDown()

// ✅ 正确写法
new Thread(() -> {
try {
// 业务逻辑
doSomething();
} catch (Exception e) {
System.err.println("任务异常:" + e.getMessage());
} finally {
latch.countDown(); // 确保在finally中调用
}
}).start();

// ❌ 错误写法
new Thread(() -> {
try {
doSomething();
latch.countDown(); // 异常时不会执行,导致死锁
} catch (Exception e) {
System.err.println("任务异常:" + e.getMessage());
// 忘记调用countDown()
}
}).start();

  1. 避免无限等待
    // 设置超时时间,避免无限等待
    boolean finished = latch.await(10, TimeUnit.SECONDS);
    if (finished) {
    System.out.println("所有任务完成");
    } else {
    System.out.println("等待超时,可能有任务失败");
    }
  2. 合理使用线程池
    public void useWithThreadPool() throws InterruptedException {
    CountDownLatch latch = new CountDownLatch(5);
    ExecutorService executor = Executors.newFixedThreadPool(3);

    for (int i = 0; i < 5; i++) {

     final int taskId = i;
     executor.submit(() -> {
         try {
             System.out.println("执行任务" + taskId);
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         } finally {
             latch.countDown();
         }
     });
    

    }

    latch.await();
    executor.shutdown(); // 关闭线程池
    System.out.println("所有任务完成");
    }
    五、实际项目案例
    案例:系统启动初始化
    public class SystemInitializer {

    public boolean initializeSystem() {

     System.out.println("开始系统初始化...");
    
     CountDownLatch latch = new CountDownLatch(4);
     AtomicBoolean success = new AtomicBoolean(true);
    
     // 数据库初始化
     new Thread(() -> {
         try {
             System.out.println("初始化数据库连接...");
             Thread.sleep(2000);
             System.out.println("数据库初始化完成");
         } catch (InterruptedException e) {
             success.set(false);
         } finally {
             latch.countDown();
         }
     }).start();
    
     // Redis初始化
     new Thread(() -> {
         try {
             System.out.println("初始化Redis连接...");
             Thread.sleep(1000);
             System.out.println("Redis初始化完成");
         } catch (InterruptedException e) {
             success.set(false);
         } finally {
             latch.countDown();
         }
     }).start();
    
     // 配置加载
     new Thread(() -> {
         try {
             System.out.println("加载系统配置...");
             Thread.sleep(800);
             System.out.println("配置加载完成");
         } catch (InterruptedException e) {
             success.set(false);
         } finally {
             latch.countDown();
         }
     }).start();
    
     // 服务注册
     new Thread(() -> {
         try {
             System.out.println("注册服务...");
             Thread.sleep(1500);
             System.out.println("服务注册完成");
         } catch (InterruptedException e) {
             success.set(false);
         } finally {
             latch.countDown();
         }
     }).start();
    
     try {
         boolean finished = latch.await(10, TimeUnit.SECONDS);
         if (finished && success.get()) {
             System.out.println("系统初始化成功!");
             return true;
         } else {
             System.out.println("系统初始化失败!");
             return false;
         }
     } catch (InterruptedException e) {
         System.out.println("初始化被中断");
         return false;
     }
    

    }

    public static void main(String[] args) {

     SystemInitializer initializer = new SystemInitializer();
     initializer.initializeSystem();
    

    }
    }
    六、总结
    CountDownLatch是Java并发编程中的实用工具,它的核心价值在于:

核心特点
简单易用:API简洁,概念清晰
线程安全:内部实现保证多线程安全
灵活应用:适合多种并发协作场景
使用要点
异常安全:在finally中调用countDown()
超时控制:使用带超时的await()方法
一次性使用:CountDownLatch不能重置
合理设计:根据实际任务数量设置计数器
适用场景
主线程等待多个子任务完成
控制多个线程同时开始执行
分段并行计算后汇总结果
系统启动时的组件初始化
掌握CountDownLatch,让你的多线程程序更加优雅和高效!

正文到此结束
该篇文章的评论功能已被站长关闭
Loading...