考试结束,班级平均分只拿到了年级第二,班主任因而问道:你们都知道世界第一高峰珠穆朗玛峰,有人知道世界第二高峰是什么吗?正当班主任要继续发话,只听到角落默默想起来一个声音:”乔戈里峰”html
文章出自:www.cnblogs.com/dudu19939/p… 这道题是群里的朋友的投稿,上面是这位朋友的原文博客连接,这道题目我在百度的面试也遇到过,当时这位朋友在这个问题的博客也与我交流过,我也贡献了其中一种方法,嘿嘿,最后看了这位朋友的成文,以为写得很不错,望与诸君共勉(PS:欢迎你们投稿)。java
下面是我在2018年10月11日二面百度的时候的一个问题:程序员
java程序,主进程须要等待多个子进程结束以后再执行后续的代码,有哪些方案能够实现? 这个需求其实咱们在工做中常常会用到,好比用户下单一个产品,后台会作一系列的处理,为了提升效率,每一个处理均可以用一个线程来执行,全部处理完成了以后才会返回给用户下单成功,欢迎你们批评指正。面试
使用Thread的join()等待全部的子线程执行完毕,主线程在执行,thread.join()把指定的线程加入到当前线程,能够将两个交替执行的线程合并为顺序执行的线程。好比在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才会继续执行线程B。bash
import java.util.Vector;
public class Test {
public static void main(String[] args) throws InterruptedException {
Vector<Thread> vector = new Vector<>();
for(int i=0;i<5;i++) {
Thread childThread= new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("子线程被执行");
}
});
vector.add(childThread);
childThread.start();
}
for(Thread thread : vector) {
thread.join();
}
System.out.println("主线程被执行");
}
复制代码
子线程被执行
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
复制代码
CountDownLatch的概念微信
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者提及到线程之间的通讯(而不是用做互斥的做用)。多线程
CountDownLatch可以使一个线程在等待另一些线程完成各自工做以后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每个线程完成本身任务后,计数器的值就会减一。当计数器的值为0时,表示全部的线程都已经完成了任务,而后在CountDownLatch上等待的线程就能够恢复执行任务。 CountDownLatch的用法并发
CountDownLatch典型用法1:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为n new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程须要等待多个组件加载完毕,以后再继续执行。ide
CountDownLatch典型用法2:实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。相似于赛跑,将多个线程放到起点,等待发令枪响,而后同时开跑。作法是初始化一个共享的CountDownLatch(1),将其计数器初始化为1,多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为0,多个线程同时被唤醒。 CountDownLatch的不足工具
CountDownLatch是一次性的,计数器的值只能在构造方法中初始化一次,以后没有任何机制再次对其设置值,当CountDownLatch使用完毕后,它不能再次被使用。
import java.util.Vector;
import java.util.concurrent.CountDownLatch;
public class Test2 {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(5);
for(int i=0;i<5;i++) {
Thread childThread= new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("子线程被执行");
latch.countDown();
}
});
childThread.start();
}
latch.await();//阻塞当前线程直到latch中的值
System.out.println("主线程被执行");
}
}
复制代码
子线程被执行
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
复制代码
这里必须注意,CylicBarrier是控制一组线程的同步,初始化的参数:5的含义是包括主线程在内有5个线程,因此只能有四个子线程,这与CountDownLatch是不同的。
countDownLatch和cyclicBarrier有什么区别呢,他们的区别:countDownLatch只能使用一次,而CyclicBarrier方法可使用reset()方法重置,因此CyclicBarrier方法能够能处理更为复杂的业务场景。
我曾经在网上看到一个关于countDownLatch和cyclicBarrier的形象比喻,就是在百米赛跑的比赛中若使用 countDownLatch的话冲过终点线一我的就给评委发送一我的的成绩,10我的比赛发送10次,若是用CyclicBarrier,则只在最后一我的冲过终点线的时候发送全部人的数据,仅仅发送一次,这就是区别。
package interview;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Test3 {
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
final CyclicBarrier barrier = new CyclicBarrier(5);
for(int i=0;i<4;i++) {
Thread childThread= new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("子线程被执行");
try {
barrier.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
childThread.start();
}
barrier.await();//阻塞当前线程直到latch中的值
System.out.println("主线程被执行");
}
}
复制代码
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
复制代码
4.使用yield方法(注意此种方法通过亲自试验证实并不可靠!)
public class Test4 {
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<5;i++) {
Thread childThread= new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("子线程被执行");
}
});
childThread.start();
}
while (Thread.activeCount() > 2) { //保证前面的线程都执行完
Thread.yield();
}
System.out.println("主线程被执行");
}
}
复制代码
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
子线程被执行
复制代码
为什么yield方法会出现这样的问题?
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程仍是有可能会被再次执行到的,并非说必定会执行其余线程而该线程在下一次中不会执行到了。
Java线程中有一个Thread.yield( )方法,不少人翻译成线程让步。顾名思义,就是说当一个线程使用了这个方法以后,它就会把本身CPU执行的时间让掉,让本身或者其它的线程运行。
打个比方:如今有不少人在排队上厕所,好不容易轮到这我的上厕所了,忽然这我的说:“我要和你们来个竞赛,看谁先抢到厕所!”,而后全部的人在同一块儿跑线冲向厕所,有多是别人抢到了,也有可能他本身有抢到了。咱们还知道线程有个优先级的问题,那么手里有优先权的这些人就必定能抢到厕所的位置吗? 不必定的,他们只是几率上大些,也有可能没特权的抢到了。
yield的本质是把当前线程从新置入抢CPU时间的”队列”(队列只是说全部线程都在一个起跑线上.并不是真正意义上的队列)。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test5 {
public static void main(String[] args) {
MyThread td = new MyThread();
//1.执行 Callable 方式,须要 FutureTask 实现类的支持,用于接收运算结果。
FutureTask<Integer> result1 = new FutureTask<>(td);
new Thread(result1).start();
FutureTask<Integer> result2 = new FutureTask<>(td);
new Thread(result2).start();
FutureTask<Integer> result3 = new FutureTask<>(td);
new Thread(result3).start();
Integer sum;
try {
sum = result1.get();
sum = result2.get();
sum = result3.get();
//这里获取三个sum值只是为了同步,并无实际意义
System.out.println(sum);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} //FutureTask 可用于 闭锁 相似于CountDownLatch的做用,在全部的线程没有执行完成以后这里是不会执行的
System.out.println("主线程被执行");
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
Thread.sleep(1000);
for (int i = 0; i <= 10; i++) {
sum += i;
}
System.out.println("子线程被执行");
return sum;
}
}
复制代码
Callable+Future最终也是以Callable+FutureTask的形式实现的。 在这种方式中调用了: Future future = executor.submit(task);
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test6 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> future1 = executor.submit(task);
Future<Integer> future2 = executor.submit(task);
//获取线程执行结果,用来同步
Integer result1 = future1.get();
Integer result2 = future2.get();
System.out.println("主线程执行");
executor.shutdown();
}
}
class Task implements Callable<Integer>{
@Override public Integer call() throws Exception {
int sum = 0;
//do something;
System.out.println("子线程被执行");
return sum;
}
}
复制代码
子线程被执行
子线程被执行
主线程执行
复制代码
1)CountDownLatch和CyclicBarrier都可以实现线程之间的等待,只不过它们侧重点不一样:
CountDownLatch通常用于某个线程A等待若干个其余线程执行完任务以后,它才执行;
而CyclicBarrier通常用于一组线程互相等待至某个状态,而后这一组线程再同时执行;
另外,CountDownLatch是不可以重用的,而CyclicBarrier是能够重用的。
2)Semaphore其实和锁有点相似,它通常用于控制对某组资源的访问权限。
CountDownLatch类其实是使用计数器的方式去控制的,不难想象当咱们初始化CountDownLatch的时候传入了一个int变量这个时候在类的内部初始化一个int的变量,每当咱们调用countDownt()方法的时候就使得这个变量的值减1,而对于await()方法则去判断这个int的变量的值是否为0,是则表示全部的操做都已经完成,不然继续等待。 实际上若是了解AQS的话应该很容易想到可使用AQS的共享式获取同步状态的方式来完成这个功能。而CountDownLatch实际上也就是这么作的。
blog.csdn.net/u011277123/… blog.csdn.net/joenqc/arti… blog.csdn.net/weixin_3855… blog.csdn.net/LightOfMira… www.cnblogs.com/baizhanshi/…
做者乔戈里亲历2019秋招,哈工大计算机本硕,百度准入职java工程师,欢迎你们关注个人微信公众号:程序员乔戈里。