多线程001 - 主线程等待子线程结束

简介: 在很多时候,我们期望实现这么一种功能:在主线程中启动一些子线程,等待所有子线程执行结束后,主线程再继续执行。比如:老板分配任务,众多工人开始工作,等所有工人完成工作后,老板进行检查。

在很多时候,我们期望实现这么一种功能:在主线程中启动一些子线程,等待所有子线程执行结束后,主线程再继续执行。比如:老板分配任务,众多工人开始工作,等所有工人完成工作后,老板进行检查。


解决方法分析:


主线程通过join等待所有子线程完成后,继续执行;

主线程知道子线程的数量、未完成子线程数量,主线程等待所有子线程完成后,才继续执行。

通过join实现

第一种方式,可以直接调用Java API中关于线程的join方法等待该线程终止,可以直接实现。


每个工人都是一个线程,其run方法是工作的实现,每个工人通过名字进行区分,具体代码如下:


package howe.demo.thread.join;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014年9月12日
 */
public class Worker extends Thread {
    private String workerName;
    public Worker(String workerName) {
        this.workerName = workerName;
    }
    /**
     * @see java.lang.Thread#run()
     */
    @Override
    public void run() {
        System.out.println(this.workerName + "正在干活...");
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (InterruptedException e) {
        }
        System.out.println(this.workerName + "活干完了!");
    }
    public String getWorkerName() {
        return workerName;
    }
}

老板招收工人,然后安排工人工作,之后等待工人工作完,检查工人的工作成果(资本家啊。。。),具体代码如下:


package howe.demo.thread.join;
import java.util.List;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014年9月12日
 */
public class Boss {
    private List<Worker> workers;
    public Boss(List<Worker> workers) {
        System.out.println("老板招收工人。");
        this.workers = workers;
    }
    public void work() {
        System.out.println("老板开始安排工人工作...");
        for (Worker worker : workers) {
            System.out.println("老板安排" + worker.getWorkerName() + "的工作");
            worker.start();
        }
        System.out.println("老板安排工作结束...");
        System.out.println("老板正在等所有的工人干完活......");
        for (Worker w : workers) {
            try {
                w.join();
            } catch (InterruptedException e) {
            }
        }
        System.out.println("工人活都干完了,老板开始检查了!");
    }
}

现在写main方法进行测试:


package howe.demo.thread.join;
import java.util.ArrayList;
import java.util.List;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014年9月12日
 */
public class JoinDemo {
    public static void main(String[] args) {
        Worker w1 = new Worker("张三");
        Worker w2 = new Worker("李四");
        Worker w3 = new Worker("王五");
        List<Worker> workers = new ArrayList<Worker>();
        workers.add(w1);
        workers.add(w2);
        workers.add(w3);
        Boss boss = new Boss(workers);
        boss.work();
        System.out.println("main方法结束");
    }
}

执行结果为:


老板招收工人。
老板开始安排工人工作...
老板安排张三的工作
老板安排李四的工作
张三正在干活...
老板安排王五的工作
李四正在干活...
老板安排工作结束...
老板正在等所有的工人干完活......
王五正在干活...
王五活干完了!
张三活干完了!
李四活干完了!
工人活都干完了,老板开始检查了!

main方法结束

通过计数器实现

第二种方式可以自己实现一种计数器,用于统计子线程总数、未完成线程数,当未完成线程数大约0,主线程等待;当未完成线程数等于0,主线程继续执行。


当然,既然我们现在想到这种方式,Java API的团队当然也会想到,JDK 1.5提供了CountDownLatch用于实现上述方法。


于是对上述的工人方法进行修改:


package howe.demo.thread.cdl;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014-9-12
 */
public class Worker extends Thread {
    private CountDownLatch downLatch;
    private String workerName;
    public Worker(CountDownLatch downLatch, String workerName) {
        this.downLatch = downLatch;
        this.workerName = workerName;
    }
    public void run() {
        System.out.println(this.workerName + "正在干活...");
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (InterruptedException ie) {
        }
        System.out.println(this.workerName + "活干完了!");
        this.downLatch.countDown();
    }
    public String getWorkerName() {
        return workerName;
    }
}

latch.countDown(),是用于在子线程执行结束后计数器减一,即未完成子线程数减一。


老板类也得做出相应的修改:


package howe.demo.thread.cdl;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014-9-12
 */
public class Boss {
    private List<Worker> workers;
    private CountDownLatch downLatch;
    public Boss(List<Worker> workers, CountDownLatch downLatch) {
        this.workers = workers;
        this.downLatch = downLatch;
    }
    public void work() {
        System.out.println("老板开始安排工人工作...");
        for (Worker worker : workers) {
            System.out.println("老板安排" + worker.getWorkerName() + "的工作");
            worker.start();
        }
        System.out.println("老板安排工作结束...");
        System.out.println("老板正在等所有的工人干完活......");
        try {
            this.downLatch.await();
        } catch (InterruptedException e) {
        }
        System.out.println("工人活都干完了,老板开始检查了!");
    }
}

latch.await(),是等待子线程结束。


编写main方法进行验证:


package howe.demo.thread.cdl;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014年9月15日
 */
public class CountDownLatchDemo {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(3);
        Worker w1 = new Worker(latch, "张三");
        Worker w2 = new Worker(latch, "李四");
        Worker w3 = new Worker(latch, "王五");
        List<Worker> workers = new ArrayList<Worker>();
        workers.add(w1);
        workers.add(w2);
        workers.add(w3);
        Boss boss = new Boss(workers, latch);
        boss.work();
        System.out.println("main方法结束");
    }
}

执行结果为:


老板开始安排工人工作...
老板安排张三的工作
老板安排李四的工作
张三正在干活...
老板安排王五的工作
李四正在干活...
老板安排工作结束...
老板正在等所有的工人干完活......
王五正在干活...
王五活干完了!
李四活干完了!
张三活干完了!
工人活都干完了,老板开始检查了!

main方法结束

使用循环栅栏CyclicBarrier实现

还有一种实现,这种方式不会阻塞主线程,但是会监听所有子线程结束。此处在上述的工人老板的场景中使用的话,代码如下:


工人类:


package howe.demo.thread.cyclicbarrier;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014年9月12日
 */
public class Worker extends Thread {
    private String workerName;
    private CyclicBarrier barrier;
    public Worker(String workerName, CyclicBarrier barrier) {
        this.workerName = workerName;
        this.barrier = barrier;
    }
    @Override
    public void run() {
        System.out.println(this.workerName + "正在干活...");
        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (InterruptedException e) {
        }
        System.out.println(this.workerName + "活干完了!");
        try {
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
    public String getWorkerName() {
        return workerName;
    }
}

老板类:


package howe.demo.thread.cyclicbarrier;
import java.util.List;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014-9-12
 */
public class Boss {
    private List<Worker> workers;
    public Boss(List<Worker> workers) {
        this.workers = workers;
    }
    public void work() {
        System.out.println("老板开始安排工人工作...");
        for (Worker worker : workers) {
            System.out.println("老板安排" + worker.getWorkerName() + "的工作");
            worker.start();
        }
        System.out.println("老板安排工作结束...");
        System.out.println("老板正在等所有的工人干完活......");
    }
}

main方法测试:


package howe.demo.thread.cyclicbarrier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
/**
 * @author liuxinghao
 * @version 1.0 Created on 2014年9月15日
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("工人活都干完了,老板开始检查了!");
            }
        });
        Worker w1 = new Worker("张三", barrier);
        Worker w2 = new Worker("李四", barrier);
        Worker w3 = new Worker("王五", barrier);
        List<Worker> workers = new ArrayList<Worker>();
        workers.add(w1);
        workers.add(w2);
        workers.add(w3);
        Boss boss = new Boss(workers);
        boss.work();
        System.out.println("main方法结束");
    }
}

执行结果为:


老板开始安排工人工作...
老板安排张三的工作
老板安排李四的工作
张三正在干活...
老板安排王五的工作
李四正在干活...
老板安排工作结束...
老板正在等所有的工人干完活......
王五正在干活...
main方法结束
李四活干完了!
王五活干完了!
张三活干完了!
工人活都干完了,老板开始检查了!

通过结果分析可以很清楚的看出,boss对象的work方法执行结束后,main方法即开始执行。(此处“老板正在等所有的工人干完活......”打印之后是“王五正在干活...”,然后才是“main方法结束”,是因为对于cpu的抢占,甚至有一定的概率是“main方法结束”会在最后打印。)


假设有这么一个功能,我们需要向数据库批量写入一些记录,然后记录这个操作使用的时间,但是我们又不想影响其他操作(即不想阻塞主线程),这个时候CyclicBarrier就派上用场了。

目录
相关文章
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
44 1
C++ 多线程之初识多线程
|
27天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3
|
27天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
17 2
|
27天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
28 2
|
27天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
33 1
|
27天前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
34 1
|
27天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
25 1
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
47 6
|
1月前
|
存储 运维 NoSQL
Redis为什么最开始被设计成单线程而不是多线程
总之,Redis采用单线程设计是基于对系统特性的深刻洞察和权衡的结果。这种设计不仅保持了Redis的高性能,还确保了其代码的简洁性、可维护性以及部署的便捷性,使之成为众多应用场景下的首选数据存储解决方案。
41 1
|
1月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
25 0
C++ 多线程之线程管理函数