工作总往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总的场景, 这时候就需要使用线程了.

一个线程启动之后, 是异步的去执行需要执行的内容的, 不会影响主线程的流程, 往往需要让主线程指定后, 等待子线程的完成。并且,主线程是要利用到子线程的返回数据进行处理。这里有2种方式:实现 Callable 接口、join() 方法

1、实现 Callable 接口

class SubThread implements Callable<Integer> {
    private Integer num1;
    private Integer num2;

    public SubThread(Integer num1, Integer num2) {
        this.num1 = num1;
        this.num2 = num2;
    }

    @Override
    public Integer call() {
        System.out.println("执行子线程");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return this.num1 + this.num2;
    }
}
  1. Callable 接口是一个泛型,实现时需要指定类型,这个类型就是 call() 方法执行后返回结果的类型
  2. call() 方法有返回值
public class ExampleOne {

    public static void main(String[] args) {
        System.out.println("主线程开始运行...");
        // 1.新建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        try {
            SubThread subThread = new SubThread(1, 1);
            // 2.调度线程去执行
            Future<Integer> submit = executorService.submit(subThread);
            // 3.阻塞,等待子线程结束
            Integer integer = submit.get();
            System.out.println(integer);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 4.关闭线程池
        executorService.shutdown();
        System.out.println("主线程结束运行...");
    }
}
  1. Future 是一个任务执行的结果, 他是一个将来时, 即一个任务执行, 立即异步返回一个 Future对象, 等到任务结束的时候, 会把值返回给这个 future 对象里面
  2. Future#get() 为一个阻塞方法。主线程执行到这后,如果子线程还没有执行完毕,则主线程会阻塞起来,等待子线程结束后再执行。

2、join()方法

class SubThreadTwo implements Runnable {
    private Integer num1;
    private Integer num2;
    private Integer result;

    public SubThreadTwo(Integer num1, Integer num2) {
        this.num1 = num1;
        this.num2 = num2;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        result = num1 + num2;
    }

    public Integer getResult() {
        return result;
    }

    public void setResult(Integer result) {
        this.result = result;
    }
}
  1. 通过构造方法可以给此线程传参
public class ExampleTwo {

    public static void main(String[] args) {
        System.out.println("主线程运行开始");
        SubThreadTwo subThreadTwo = new SubThreadTwo(1, 1);
        Thread thread = new Thread(subThreadTwo);
        thread.start();
        try {
            // 1.让主线程进入阻塞状态
            thread.join();
            // 2.子线程完成后主线程被唤醒,getResult()方法获得子线程计算的值
            System.out.println(subThreadTwo.getResult());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程运行结束");
    }

}
  1. join():把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程

3、CountDownLanch

上面两种情况在线程数为一两个的时候,还可以,如果需要控制的线程数很多的话,再采取这种方式就有点过意不去了。

第一种方法, 你要调用很多个线程的 join(),特别是当你的线程不是for循环创建的, 而是一个一个创建的时候.

第二种方法, 要调用很多的 futureget()方法, 同第一种方法

CountDownLanch:

CountDownLanch 是一个倒数计数器, 给一个初始值(>=0), 然后每一次调用 countDown 就会减1,这很符合等待多个子线程结束的场景: 一个线程结束的时候,countDown 一次,直到所有的线程都countDown了,那么所有子线程就都结束了。

await(): 会阻塞等待计数器减少到0位置. 带参数的await是多了等待时间.

countDown(): 将当前的计数减1

getCount(): 返回当前的计数

显而易见, 我们只需要在子线程执行之前, 赋予初始化 countDownLanch,并赋予线程数量为初始值.

每个线程执行完毕的时候, 就 countDown 一下.主线程只需要调用 await() 方法, 可以等待所有子线程执行结束

class SubThreadThree extends Thread {

    private CountDownLatch countDownLatch;

    public SubThreadThree(CountDownLatch countDownLatch) {
        this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
        try {
            System.out.println("子线程开始执行");
            Thread.sleep(2000);
            System.out.println("子线程结束执行");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程结束时,将计时器减一
            countDownLatch.countDown();
        }

    }
}

CountDownTest: CountDownLatch 计数器的用法:

public class CountDownTest {

    public static void main(String[] args) {
        System.out.println("主线程开始启动");
        // 1.定义线程数
        int subThreadNum = 3;
        // 2.取得一个倒计时器,从3开始
        CountDownLatch countDownLatch = new CountDownLatch(subThreadNum);
        // 3.依次创建3个线程,并启动
        /*for (int i = 0; i < subThreadNum; i++) {
            new SubThreadThree(countDownLatch).start();
        }*/
        
        Thread t1 = new SubThreadThree(countDownLatch);
        // 异步(开启线程)去执行第一个任务
        t1.start();
        Thread t2 = new SubThreadThree(countDownLatch);
        // 异步(开启线程)去执行第二个任务
        t2.start();
        Thread t3 = new SubThreadThree(countDownLatch);
        // 异步(开启线程)去执行第三个任务
        t3.start();
        
        try {
            Thread.sleep(2000);
            // 4.等待所有的子线程结束
            countDownLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("主线程开始停止");
    }
}

线程 t1t2t3 并发地异步执行任务,直到这三个任务执行完毕,主线程才会继续往下走。否则,它会阻塞。