join方法与countDownLatch与CyclicBarrier的区别

本文详细介绍了Java中三种重要的线程控制技术:join方法、CountDownLatch和CyclicBarrier的使用方法及工作原理。通过示例代码展示了如何利用这些技术来协调多线程之间的执行顺序,确保任务按预期进行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

join方法:

当前线程中调用thread.join()会导致当前线程阻塞,此时只有当thread线程执行完后,当前线程才能继续往下执行。
join的工作原理是,不停检查thread是否存活,如果存活则让当前线程永远wait,直到thread线程终止
可以看看join的源码:

 public final synchronized void join(long l)
        throws InterruptedException
    {
        long l1 = System.currentTimeMillis();
        long l2 = 0L;
        if(l < 0L)
            throw new IllegalArgumentException("timeout value is negative");
        if(l == 0L)
//当线程是存活状态,则会一直等待下去
            for(; isAlive(); wait(0L));
        else
            do
            {
                if(!isAlive())
                    break;
                long l3 = l - l2;
                if(l3 <= 0L)
                    break;
                wait(l3);
                l2 = System.currentTimeMillis() - l1;
            } while(true);
    }

我们可以看看具体例子:

  public class JoinTest extends Thread {  

    //工作者名  
    private String name;  
    //工作时间  
    private long time;  

    public JoinTest(String name, long time) {  
        this.name = name;  
        this.time = time;  
    }  

    @Override  
    public void run() {  
        // TODO 自动生成的方法存根  
        try {  
            System.out.println(name+"开始工作");  
            Thread.sleep(time);  
            System.out.println(name+"工作完成,耗费时间="+time);  
        } catch (InterruptedException e) {  
            // TODO 自动生成的 catch 块  
            e.printStackTrace();  
        }     
    }  
}  

public static void main(String[] args) throws ParseException, InterruptedException
{

    JoinTest worker0 = new JoinTest("worker0", 5000);  
    JoinTest worker1 = new JoinTest("worker1", 5000);  
    JoinTest worker2 = new JoinTest("worker2", 5000);  

    worker0.start();  
    worker1.start();  

    worker0.join();  
    worker1.join();  
    System.out.println("准备工作就绪");  

    worker2.start();      

}
控制台输出顺序如下:
这里写图片描述

观察控制台,可以明确看出,只有当线程worker0,worker1执行完毕之后,worker2才会开始执行。

countDownLatch:

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

**构造方法摘要**
CountDownLatch(int count) 
构造一个用给定计数初始化的 CountDownLatch。

方法摘要

  1. void | await()
    使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。
  2. boolean | await(long timeout, TimeUnit unit)
    使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
  3. void | countDown()
    递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
  4. long | getCount()
    返回当前计数。
  5. String | toString()
    返回标识此锁存器及其状态的字符串。

CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
countDown 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
如果当前计数等于零,则不发生任何操作。
如上面join的例子,将之改成为CountDownLatch

   public class JoinTest extends Thread {  

        //工作者名  
        private String name;  
        //工作时间  
        private long time;  

        CountDownLatch count;
        public JoinTest(String name, long time,CountDownLatch count) {  
            this.name = name;  
            this.time = time;  
            this.count = count;
        }  

        @Override  
        public void run() {  
            System.out.println(name+"开始工作");  
            count.countDown();
            System.out.println(name+"工作完成,耗费时间="+time);     
        }  
    }  


  public static void main(String[] args) throws ParseException, InterruptedException
    {
        CountDownLatch count = new CountDownLatch(2);
        JoinTest worker0 = new JoinTest("worker0", 5000,count);  
        JoinTest worker1 = new JoinTest("worker1", 5000,count);  
        JoinTest worker2 = new JoinTest("worker2", 5000,count);  

        worker0.start();  
        worker1.start();  
        count.await();
        System.out.println("准备工作就绪");  

        worker2.start();   
}

这里写图片描述

从控制台结果可以看出,执行效果还是一样的。count计数器为零之前,当前线程会一直在等待着。
项目中countDown可运用大批量数据处理,例如处理100万的数据,可以将100万拆分成100个线程放入线程池中,计数器为100,只有当这100万的数据全部处理完毕,线程往下执行,告之数据已经全部处理完毕

  int rum = 0;
    CountDownLatch countDownLatch;
    public CountDownLatchTest(CountDownLatch countDownLatch,int rum)
    {
        this.countDownLatch = countDownLatch;
        this.rum = rum;
    }
    @Override
    public void run()
    {
        // TODO Auto-generated method stub
        super.run();
        System.out.println("第"+rum+"批数据开始处理");
        countDownLatch.countDown();
    }

    public static void main(String[] args) throws InterruptedException
    {
        // TODO Auto-generated method stub
        //假设有100万的数据需要处理,分成10个线程处理
        CountDownLatch count = new CountDownLatch(10);
        for(int i =0 ;i<10;i++){
            CountDownLatchTest a = new CountDownLatchTest(count,i);
            a.start();
        }
        //处理完成之前所有线程一直等待着
        count.await();
        System.out.println("处理完毕");
    }

这里写图片描述

CyclicBarrier:

类似于CountDownLatch,它也是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

  • CyclicBarrier(int parties)
    创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier 时执行预定义的操作。
  • CyclicBarrier(int parties, Runnable barrierAction)
    创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

方法摘要
int | await()
在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
int | await(long timeout, TimeUnit unit)
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。
int | getNumberWaiting()
返回当前在屏障处等待的参与者数目。
int | getParties()
返回要求启动此 barrier 的参与者数目。
boolean | isBroken()
查询此屏障是否处于损坏状态。
void | reset()
将屏障重置为其初始状态。

在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
如果当前线程不是将到达的最后一个线程,出于调度目的,将禁用它,且在发生以下情况之一前,该线程将一直处于休眠状态:
● 最后一个线程到达;或者
● 其他某个线程中断当前线程;或者
● 其他某个线程中断另一个等待线程;或者
● 其他某个线程在等待 barrier 时超时;或者
● 其他某个线程在此 barrier 上调用 reset()。
如果当前线程:
● 在进入此方法时已经设置了该线程的中断状态;或者
● 在等待时被中断
则抛出 InterruptedException,并且清除当前线程的已中断状态。
如果在线程处于等待状态时 barrier 被 reset(),或者在调用 await 时 barrier 被损坏,抑或任意一个线程正处于等待状态,则抛出 BrokenBarrierException 异常。
如果任何线程在等待时被 中断,则其他所有等待线程都将抛出 BrokenBarrierException 异常,并将 barrier 置于损坏状态。
如果当前线程是最后一个将要到达的线程,并且构造方法中提供了一个非空的屏障操作,则在允许其他线程继续运行之前,当前线程将运行该操作。如果在执行屏障操作过程中发生异常,则该异常将传播到当前线程中,并将 barrier 置于损坏状态。

CyclicBarrier 处理方式可以看做并行,简单例子,比如赛跑,当所有运动员准备就绪时,裁判发枪了,所有人同时开始起跑,如果一个运动员还没准备好,那么裁判会让所有运动员会等他准备好之后再开始发枪。

public class CyclicBarrierTest
{
    public static void main(String[] args) throws IOException, InterruptedException {  
        //如果将参数改为4,但是下面只加入了3个选手,这永远等待下去  
        //Waits until all parties have invoked await on this barrier.   
        CyclicBarrier barrier = new CyclicBarrier(3);  

        ExecutorService executor = Executors.newFixedThreadPool(3); 
        executor.submit(new Thread(new Runner(barrier, "1号选手",5000)));  

        executor.submit(new Thread(new Runner(barrier, "2号选手",10000)));  
        executor.submit(new Thread(new Runner(barrier, "3号选手",3000)));  
        executor.shutdown();  
    }  


}

class Runner extends Thread {  
    // 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)  
    private CyclicBarrier barrier;  

    private String name;  

    private long time;  

    public Runner(CyclicBarrier barrier, String name, long time) {  
        super();  
        this.barrier = barrier;  
        this.name = name;
        this.time = time;  

    }  

    @Override  
    public void run() {  
        try {  
            Thread.sleep(time);  
            System.out.println(name + " 准备好了...");  
            // barrier的await方法,在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。
            System.out.println("在此之前已经有" +barrier.getNumberWaiting()+ "位选手 准备好了...");    
            barrier.await();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        } catch (BrokenBarrierException e) {  
            e.printStackTrace();  
        }  
        System.out.println(name + " 裁判发枪,起跑!");  
    }  
}  

这里写图片描述

<think>好的,用户问的是Thread.join、sleep和CountDownLatch区别。我需要先回忆这三个Java并发工具的作用和使用场景。 首先,Thread.join的作用是让当前线程等待另一个线程执行完毕。比如线程A调用了线程B的join方法,A会阻塞直到B完成。这适用于线程顺序执行的场景,比如主线程需要等待子线程完成任务后再继续。 然后是sleep,这个比较简单,是让当前线程暂停执行一段时间,不释放锁。主要用于模拟延迟或定时操作,但需要注意异常处理。 CountDownLatch是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。它通过计数器来实现,当计数器减到零时,等待的线程被释放。适用于多个线程协作完成任务的场景,比如并发测试时等待所有线程准备好再开始。 接下来要比较它们的区别。首先功能上,join线程级别的等待,sleep是暂停当前线程CountDownLatch是基于计数的协作。其次使用场景不同,join用于线程顺序,sleep用于定时,CountDownLatch用于多线程同步。另外,是否释放锁方面,sleep不释放,join也不释放,而CountDownLatch的await方法会释放锁(如果是基于AQS实现的话)。 还要注意CountDownLatch是一次性的,计数器不能重置,而CyclicBarrier可以循环使用。不过用户没提到CyclicBarrier,暂时不需要比较。用户可能是在开发多线程应用时遇到了选择哪个工具的问题,需要明确各自适用的情况。 可能用户需要实际的例子来区分。比如用join来确保线程执行顺序,用sleep控制时间间隔,用CountDownLatch做并发测试的起跑线。另外,异常处理方面,join和sleep都需要处理InterruptedException,而CountDownLatch的await方法同样需要处理异常。 还要考虑它们的底层实现。join内部可能基于Object的wait方法,sleep是Thread方法,而CountDownLatch基于AQS。这可能影响性能和使用方式。 最后,总结它们的核心区别,从功能、使用场景、是否释放锁、可重用性等方面对比。确保用户能清楚在不同情况下如何选择。 </think>### **Thread.join、sleep CountDownLatch区别详解** | **特性** | **`Thread.join()`** | **`Thread.sleep()`** | **`CountDownLatch`** | |------------------------|-----------------------------------------|-----------------------------------------|------------------------------------------| | **功能** | 等待**目标线程执行完成** | **暂停当前线程**指定时间 | 等待**多个线程或操作完成**(计数器归零) | | **适用场景** | 线程顺序执行(如主线程等待子线程完成) | 模拟延迟、定时任务 | 多线程协作(如并发测试、任务分阶段启动) | | **是否释放锁** | 否(持有锁时阻塞) | 否(不涉及锁) | 是(`await()`会释放锁,进入等待队列) | | **阻塞对象** | 当前线程等待**目标线程** | 当前线程等待**自身暂停** | 当前线程等待**计数器归零** | | **可重用性** | 一次性(线程结束后自动释放) | 可重复调用 | 一次性(计数器归零后不可重置) | | **异常处理** | 需捕获`InterruptedException` | 需捕获`InterruptedException` | 需捕获`InterruptedException` | | **底层实现** | 基于`Object.wait()`实现 | JVM本地方法实现 | 基于AQS(AbstractQueuedSynchronizer) | --- ### **具体使用对比** #### **1. `Thread.join()`** ```java public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { // 子线程任务 System.out.println("子线程运行中..."); }); t.start(); t.join(); // 主线程等待t执行完毕 System.out.println("主线程继续执行"); } } ``` - **特点**: - 适用于**线程间顺序依赖**(如A线程必须等B线程完成才能执行)。 - `join()`可带超时参数(`join(long millis)`),避免永久阻塞。 --- #### **2. `Thread.sleep()`** ```java public class SleepExample { public static void main(String[] args) { try { System.out.println("主线程暂停2秒"); Thread.sleep(2000); System.out.println("主线程恢复"); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` - **特点**: - 用于**定时控制**或模拟延迟(如网络请求重试)。 - 不涉及线程协作,仅暂停当前线程。 --- #### **3. `CountDownLatch`** ```java public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { int threadCount = 3; CountDownLatch latch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { System.out.println("线程执行任务"); Thread.sleep(1000); // 模拟任务耗时 } catch (InterruptedException e) { e.printStackTrace(); } finally { latch.countDown(); // 任务完成,计数器减1 } }).start(); } latch.await(); // 等待所有线程完成 System.out.println("所有线程任务完成"); } } ``` - **特点**: - 适用于**多线程协作**(如并发测试、分阶段任务)。 - 通过`countDown()`减少计数器,通过`await()`阻塞等待计数器归零。 --- ### **核心区别总结** 1. **线程协作 vs 单线程控制** - `join()`:线程间**一对一等待**(A等B)。 - `sleep()`:**单线程暂停**,不涉及协作。 - `CountDownLatch`:**多线程协作**(A等B、C、D全部完成)。 2. **灵活性** - `join()`和`sleep()`功能单一,`CountDownLatch`支持复杂同步场景(如并发测试起跑线、资源初始化等待)。 3. **性能影响** - `sleep()`不释放资源,可能导致资源占用; - `CountDownLatch`基于AQS,性能更优且支持并发控制。 --- ### **选择建议** - **顺序执行**:使用`join()`(如主线程等待子线程)。 - **定时延迟**:使用`sleep()`(如定时任务、重试机制)。 - **多线程协作**:使用`CountDownLatch`(如并发测试、批量任务完成通知)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值