场景介绍
在实际的工作过程中,为了减少用户的等待时间,通常会使用多线程去并行处理相关任务。主任务线程等待其他并行任务处理完成后,获取执行的结果,经过相关处理,返回给用户。这种方式是多线程在程序中主要的使用方式,因此这就要求主线程必须等待任务线程的执行,然后汇总结果。
实现方法
join方法
在 CountDownLatch 类未出现之前要实现主线程等待只能使用 join 方法(这样说有点绝对,因为 Future 的 get 方法也能实现闭锁的功能,但是需要自己将其缓存后再去循环处理,不是使用 JDK 所提供的方法了)。join 方法和之前介绍的方法不同,它是线程 Thread 类的方法,它没有入参也没有返回值。
示例代码
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
Thread t1 = new Thread(() -> {
System.out.println("t1 start");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
});
Thread t2 = new Thread(() -> {
System.out.println("t2 start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2 end");
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main end");
}
如上代码中,创建了两个子线程 t1 和 t2, 然后分别启动两个子线程并调用各自的 join 方法。方法执行后,会先执行两个子线程,执行完后才会执行主线程的打印任务。
执行过程为,当主线程执行到 t1.join() 的时候其会被阻塞,等待 t1 执行完后再执行 t2.join() 会再次被阻塞,等到 t2 执行完后,再执行主线程的打印任务。
需要注意的是,要先启动后再调用 join 方法才会有用,如果先调用 join 方法再启动是不会生效的。
CountDownLatch
上面我们介绍了通过使用线程类的 join 方法来让主线程等待,但是这个方法不够灵活,对实际工作的各种场景并不能完全满足,因此 JDK 提供了 CountDownLatch 类,可以让我们能更好的实现该功能。
示例代码
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
try {
System.out.println("t1 start");
Thread.sleep(500);
System.out.println("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
new Thread(() -> {
try {
System.out.println("t2 start");
Thread.sleep(1000);
System.out.println("t2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
countDownLatch.await();
System.out.println("main end");
}
}
如上代码中,创建了一个 CountDownLatch 对象,由于有两个子线程,所以构造函数传入的参数为2。然后创建了两个子线程,在各自的 finally 模块中调用 CountDownLatch 对象的 countDown 方法;分别启动两个子线程,最后由主线程调用 CountDownLatch 对象的 await 方法,并执行打印任务。
代码的执行过程为,在创建 CountDownLatch 对象时,构造方法传入了计数器数量2,主线程调用 CountDownLatch 对象的 await 方法时将会被阻塞,直到 CountDownLatch 对象中的计数器变为0,主线程才会返回,阻塞也就结束了。在子线程执行时,由于调用了 CountDownLatch 对象的 countDown 方法,每次调用计数器都会减1 ,两次调用后计数器变为0,主线程结束阻塞打印日志。
需要注意的是,CountDownLatch 对象的 await 方法被返回时,只需要 CountDownLatch 对象的计数器变为0即可,所以只需要调用 countDown 方法的次数为计数器的数量即可,并不需要各线程都执行完成,示例代码如下:
public static void main(String[] args) throws InterruptedException {
System.out.println("main start");
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
try {
System.out.println("t1 start");
Thread.sleep(2000);
System.out.println("t1 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
new Thread(() -> {
try {
System.out.println("t2 start");
Thread.sleep(1000);
System.out.println("t2 end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
countDownLatch.countDown();
}
}).start();
countDownLatch.await();
System.out.println("main end");
}
总结
1、join 方法是 Thread 类的方法,调用它时主线程会阻塞到调用它的线程执行完后才执行。
2、CountDownLatch 类是 JDK 专门提供的一个用来操作线程等待的类,它是使用计数器的方法,并不关心各线程是否执行完成,因此它更加的灵活。
3、由于在实际工作中,常常使用线程池来管理程序中的线程,因此使用 CountDownLatch 类来处理会更加灵活实用。
网友评论