考试结束,班级平均分只拿到了年级第二,班主任于是问道:大家都知道世界第一高峰珠穆朗玛峰,有人知道世界第二高峰是什么吗?正当班主任要继续发话,只听到角落默默想起来一个声音:”乔戈里峰

前言

文章出自:www.cnblogs.com/dudu19939/p… 这道题是群里的朋友的投稿,上面是这位朋友的原文博客链接,这道题目我在百度的面试也遇到过,当时这位朋友在这个问题的博客也与我交流过,我也贡献了其中一种方法,嘿嘿,最后看了这位朋友的成文,觉得写得很不错,望与诸君共勉(PS:欢迎大家投稿)。

题目

下面是我在2018年10月11日二面百度的时候的一个问题:

java程序,主进程需要等待多个子进程结束之后再执行后续的代码,有哪些方案可以实现? 这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个处理都可以用一个线程来执行,所有处理完成了之后才会返回给用户下单成功,欢迎大家批评指正。

解法

1.join方法

使用Thread的join()等待所有的子线程执行完毕,主线程在执行,thread.join()把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才会继续执行线程B。

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() {@Overridepublic void run() {// TODO Auto-generated method stubtry {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("子线程被执行");}});vector.add(childThread);childThread.start();}for(Thread thread : vector) {thread.join();}System.out.println("主线程被执行");}
复制代码

执行结果

子线程被执行
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
复制代码

2.等待多线程完成的CountDownLatch

CountDownLatch的概念

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。 CountDownLatch的用法

CountDownLatch典型用法1:某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为n new CountDownLatch(n) ,每当一个任务线程执行完毕,就将计数器减1 countdownlatch.countDown(),当计数器的值变为0时,在CountDownLatch上 await() 的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

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() {@Overridepublic void run() {// TODO Auto-generated method stubtry {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("子线程被执行");latch.countDown();}});childThread.start();}latch.await();//阻塞当前线程直到latch中的值System.out.println("主线程被执行");}}
复制代码

执行结果:

子线程被执行
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
复制代码

3.同步屏障CyclicBarrier

这里必须注意,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() {@Overridepublic void run() {// TODO Auto-generated method stubtry {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("子线程被执行");try {barrier.await();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (BrokenBarrierException e) {// TODO Auto-generated catch blocke.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() {@Overridepublic void run() {// TODO Auto-generated method stubtry {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("子线程被执行");}});childThread.start();}while (Thread.activeCount() > 2) {  //保证前面的线程都执行完Thread.yield();}System.out.println("主线程被执行");}
}
复制代码

执行结果:

子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
子线程被执行
复制代码

为何yield方法会出现这样的问题?

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。

Java线程中有一个Thread.yield( )方法,很多人翻译成线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。

打个比方:现在有很多人在排队上厕所,好不容易轮到这个人上厕所了,突然这个人说:“我要和大家来个竞赛,看谁先抢到厕所!”,然后所有的人在同一起跑线冲向厕所,有可能是别人抢到了,也有可能他自己有抢到了。我们还知道线程有个优先级的问题,那么手里有优先权的这些人就一定能抢到厕所的位置吗? 不一定的,他们只是概率上大些,也有可能没特权的抢到了。

yield的本质是把当前线程重新置入抢CPU时间的”队列”(队列只是说所有线程都在一个起跑线上.并非真正意义上的队列)。

5.FutureTast可用于闭锁,类似于CountDownLatch的作用

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 blocke.printStackTrace();} catch (ExecutionException e) {// TODO Auto-generated catch blocke.printStackTrace();}  //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的System.out.println("主线程被执行");}}class MyThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;Thread.sleep(1000);for (int i = 0; i <= 10; i++) {sum += i;}System.out.println("子线程被执行");return sum;}
}
复制代码

6.使用callable+future

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工程师,欢迎大家关注我的微信公众号:程序员乔戈里

百度的一道 java 高频面试题的多种解法相关推荐

  1. Java高频面试题(四)

    Java高频面试题四 六. 框架部分 6.1.什么是框架? 6.2 .MVC模式 6.3. MVC框架 6.4. 简单讲一下struts2的执行流程? 6.5. Struts2中的拦截器,你都用它干什 ...

  2. Java高频面试题(面向对象)

    Java高频面试题(面向对象) 面向对象 2.1 类与对象的区别 类是一类事物的整体描述,是一个模板,是对象的抽象,是具有相同行为和特征的对象的描述 对象具体的一个事物,是类的实例化,万事万物皆对象 ...

  3. 【Java】Java 高频面试题英语版(1)

      今天分享 Java 高频面试题英语版.音频文件放在下方,点击获取. [Java]Java 高频面试题英语版(1) [Java]Java 高频面试题英语版(2) [Java]Java 高频面试题英语 ...

  4. 100道Java高频面试题(阿里面试官整理)

    我分享文章的时候,有个读者回复说他去年就关注了我的微信公众号,打算看完我的所有文章,然后去面试,结果我后来很长时间不更新了...所以为了弥补一直等我的娃儿们,给大家的金三银四准备了100道花时间准备的 ...

  5. java高频面试题(2023最新)

    目录 一.java基础 1.八大基础类型 2.java三大特性 3.重载和重写的区别 4.pubilc.protected.(dafault)不写.private修饰符的作用范围 5.==和equal ...

  6. Java高频面试题汇总(2022)

    Java 1. ArrayList和LinkedList的区别 Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的. Array获取数据的时间复杂度是O(1 ...

  7. Java高频面试题总结

    本文目录: Java的特点 Java 与 C++ 的区别 面向对象和面向过程的区别? JKD和JRE的区别? 面向对象有哪些特性? Java的基本数据类型有哪些? 什么是值传递和引用传递? 自动装箱和 ...

  8. 五面拿下阿里飞猪offer,熬夜整理Java高频面试题

    前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题.其实笔者本人对这类框架源码题还是持一定的怀疑态度的.如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null ...

  9. 腾讯+阿里+百度Java高频面试题(涵盖了年薪20W80W的高频面试题)

    性能优化面试专栏 tomcat性能优化整理 1.你怎样给tomcat调优 2.如何加大comcat连接数 3.怎样加大tomcat的内存 4.tomcat中如何禁止列目录下的文件 5.Tomcat有几 ...

最新文章

  1. 设计模式:单例模式在JDK中的应用
  2. 免费OA系统品牌有那些?
  3. IOI1999 花店橱窗布置
  4. angular5.0封装underscore常用pipe并发布到npm全套流程
  5. 求圆面积的python代码_《求》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...
  6. .net 链oracle,.net链数据库oracle
  7. 生成内核版本号头文件的方法
  8. textContent和innerText属性的区别
  9. thinkPHP 表单自动验证功能
  10. html 时间控件 只选择年,js时间控件只显示年月
  11. 访问局域网计算机切换用户,Win7切换用户账户访问共享文件夹的方法
  12. laravel框架中hasOne和blongTo的用法详解
  13. Rhythmbox中mp3中文乱码解决
  14. Lecture 8:Norms of Vectors and Matrices
  15. python摇骰子游戏小案例
  16. 2022网鼎杯青龙组wp
  17. Android安装busybox
  18. 苏州大学2021年全日制博士学位研究生招生简章
  19. [置顶] 忆往昔,看今朝(2012-2013年总结)
  20. 前端入门练习之将psd文件转换为HTML文件

热门文章

  1. 《Essential C++》笔记之关联容器set的使用总结
  2. html界面选择按钮没法取消,如何使用JavaScript取消选择按钮
  3. java处理超大csv文件_比较 csv 文件中数据差异
  4. 怀旧服小号最多的服务器,魔兽世界怀旧服小号战场将成为GZS量产高督的基地?...
  5. linux test 使用方法,Linux系统test命令使用方法介绍
  6. file对象怎样获取文件的长度?_使用FSO对象获取整个文件夹的信息
  7. 如何提取html的文本,如何从html标签之间提取文本?
  8. linux socket编程web服务器实现报文解析,[Socket][网络编程]程序范例:Linux下连接WEB服务器...
  9. hadoop 配置 docker伪分布式(单节点)
  10. pytorch nn.LogSoftmax