1 2 3 4 5 6 7 8 9 10 11 12 public class TestThread extends Thread { public void run () { System.out.println(this .getName() + "子线程开始" ); try { Thread.sleep(5000 ); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println(this .getName() + "子线程结束" ); } }
首先是一个线程,它执行完成需要 5 秒。
1、主线程等待一个子线程
1 2 3 4 5 6 7 8 9 10 11 public class Main {public static void main (String[] args) {long start = System.currentTimeMillis();Thread thread = new TestThread(); thread.start(); long end = System.currentTimeMillis();System.out.println("子线程执行时长:" + (end - start)); } }
在主线程中,需要等待子线程执行完成。但是执行上面的 main 发现并不是想要的结果:
子线程执行时长:0 Thread-0 子线程开始 Thread-0 子线程结束
很明显主线程和子线程是并发执行的,主线程并没有等待。
对于只有一个子线程,如果主线程需要等待子线程执行完成,再继续向下执行,可以使用 Thread 的 join()方法。join()方法会阻塞主线程继续向下执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Main {public static void main (String[] args) {long start = System.currentTimeMillis();Thread thread = new TestThread(); thread.start(); try { thread.join(); } catch (InterruptedException e){ e.printStackTrace(); } long end = System.currentTimeMillis();System.out.println("子线程执行时长:" + (end - start)); } }
执行结果:
Thread-0 子线程开始 Thread-0 子线程结束 子线程执行时长:5000
注意:join()要在 start()方法之后调用。
2、主线程等待多个子线程
比如主线程需要等待 5 个子线程。这 5 个线程之间是并发执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Main {public static void main (String[] args) {long start = System.currentTimeMillis();for (int i = 0 ; i < 5 ; i++){ Thread thread = new TestThread(); thread.start(); try { thread.join(); } catch (InterruptedException e){ e.printStackTrace(); } } long end = System.currentTimeMillis();System.out.println("子线程执行时长:" + (end - start)); } }
在上面的代码套上一个 for 循环,执行结果:
Thread-0 子线程开始 Thread-0 子线程结束 Thread-1 子线程开始 Thread-1 子线程结束 Thread-2 子线程开始 Thread-2 子线程结束 Thread-3 子线程开始 Thread-3 子线程结束 Thread-4 子线程开始 Thread-4 子线程结束 子线程执行时长:25000
由于 thread.join()阻塞了主线程继续执行,导致 for 循环一次就需要等待一个子线程执行完成,而下一个子线程不能立即 start(),5 个子线程不能并发。
要想子线程之间能并发执行,那么需要在所有子线程 start()后,在执行所有子线程的 join()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Main {public static void main (String[] args) {long start = System.currentTimeMillis();List<Thread> list = new ArrayList<Thread>(); for (int i = 0 ; i < 5 ; i++){ Thread thread = new TestThread(); thread.start(); list.add(thread); } try { for (Thread thread : list){ thread.join(); } } catch (InterruptedException e){ e.printStackTrace(); } long end = System.currentTimeMillis();System.out.println("子线程执行时长:" + (end - start)); } }
执行结果:
Thread-0 子线程开始 Thread-3 子线程开始 Thread-1 子线程开始 Thread-2 子线程开始 Thread-4 子线程开始 Thread-3 子线程结束 Thread-0 子线程结束 Thread-2 子线程结束 Thread-1 子线程结束 Thread-4 子线程结束 子线程执行时长:5000
3、主线程等待多个子线程(CountDownLatch 实现)
CountDownLatch 是 Java.util.concurrent 中的一个同步辅助类,可以把它看做一个倒数计数器,就像神舟十号发射时倒数:10,9,8,7….2,1,0,走你。初始化时先设置一个倒数计数初始值,每调用一次 countDown()方法,倒数值减一,await()方法会阻塞当前进程,直到倒数至 0。
同样还是主线程等待 5 个并发的子线程。修改上面的代码,在主线程中,创建一个初始值为 5 的 CountDownLatch,并传给每个子线程,在每个子线程最后调用 countDown()方法对倒数器减 1,当 5 个子线程等执行完成,那么 CountDownLatch 也就倒数完成,主线程调用 await()方法等待 5 个子线程执行完成。
修改 MyThread 接收传入的 CountDownLatch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class TestThread extends Thread {private CountDownLatch countDownLatch;public TestThread (CountDownLatch countDownLatch) {this .countDownLatch = countDownLatch;} public void run () {System.out.println(this .getName() + "子线程开始" ); try { Thread.sleep(5000 ); } catch (InterruptedException e){ e.printStackTrace(); } System.out.println(this .getName() + "子线程结束" ); countDownLatch.countDown(); } }
修改 main:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Main {public static void main (String[] args) {long start = System.currentTimeMillis();CountDownLatch countDownLatch = new CountDownLatch(5 ); for (int i = 0 ; i < 5 ; i++){ Thread thread = new TestThread(countDownLatch); thread.start(); } try { countDownLatch.await(); } catch (InterruptedException e){ e.printStackTrace(); } long end = System.currentTimeMillis();System.out.println("子线程执行时长:" + (end - start)); } }
执行结果: Thread-0 子线程开始 Thread-2 子线程开始 Thread-1 子线程开始 Thread-3 子线程开始 Thread-4 子线程开始 Thread-2 子线程结束 Thread-4 子线程结束 Thread-1 子线程结束 Thread-0 子线程结束 Thread-3 子线程结束 子线程执行时长:5000
注意:如果子线程中会有异常,那么 countDownLatch.countDown()应该写在 finally 里面,这样才能保证异常后也能对计数器减 1,不会让主线程永远等待。
另外,await()方法还有一个实用的重载方法:public booleanawait(long timeout, TimeUnit unit),设置超时时间。
例如上面的代码,想要设置超时时间 10 秒,到了 10 秒无论是否倒数完成到 0,都会不再阻塞主线程。返回值是 boolean 类型,如果是超时返回 false,如果计数到达 0 没有超时返回 true。
1 2 3 4 5 6 7 8 9 10 11 boolean timeoutFlag = countDownLatch.await(10 ,TimeUnit.SECONDS);if (timeoutFlag){ System.out.println("所有子线程执行完成" ); } else { System.out.println("超时" ); }
4、主线程等待线程池 Java 线程池 java.util.concurrent.ExecutorService 是很好用的多线程管理方式。ExecutorService 的一个方法 boolean awaitTermination(long timeout, TimeUnit unit),即阻塞主线程,等待线程池的所有线程执行完成,用法和上面所说的 CountDownLatch 的 public boolean await(long timeout,TimeUnit unit)类似,参数设置一个超时时间,返回值是 boolean 类型,如果超时返回 false,如果线程池中的线程全部执行完成,返回 true。
由于 ExecutorService 没有类似 CountDownLatch 的无参数的 await()方法,只能通过 awaitTermination 来实现主线程等待线程池。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Main {public static void main (String[] args) {long start = System.currentTimeMillis();ExecutorService executor = Executors.newFixedThreadPool(2 ); for (int i = 0 ; i < 5 ; i++){ Thread thread = new TestThread(); executor.execute(thread); } executor.shutdown(); try { while (!executor.awaitTermination(10 , TimeUnit.SECONDS));} catch (InterruptedException e){ e.printStackTrace(); } long end = System.currentTimeMillis();System.out.println("子线程执行时长:" + (end - start)); } }
执行结果: Thread-0 子线程开始 Thread-1 子线程开始 Thread-0 子线程结束 Thread-2 子线程开始 Thread-1 子线程结束 Thread-3 子线程开始 Thread-2 子线程结束 Thread-4 子线程开始 Thread-3 子线程结束 Thread-4 子线程结束 子线程执行时长:15000
另外,while(!executor.isTerminated())也可以替代上面的 while (!executor.awaitTermination(10,TimeUnit.SECONDS)),isTerminated()是用来判断线程池是否执行完成。但是二者比较我认为还是 awaitTermination 更好,它有一个超时时间可以控制每隔多久循环一次,而不是一直在循环来消耗性能。