百度的一道 java 高频面试题的多种解法
考试结束,班级平均分只拿到了年级第二,班主任于是问道:大家都知道世界第一高峰珠穆朗玛峰,有人知道世界第二高峰是什么吗?正当班主任要继续发话,只听到角落默默想起来一个声音:”乔戈里峰”
前言
文章出自: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 高频面试题的多种解法相关推荐
- Java高频面试题(四)
Java高频面试题四 六. 框架部分 6.1.什么是框架? 6.2 .MVC模式 6.3. MVC框架 6.4. 简单讲一下struts2的执行流程? 6.5. Struts2中的拦截器,你都用它干什 ...
- Java高频面试题(面向对象)
Java高频面试题(面向对象) 面向对象 2.1 类与对象的区别 类是一类事物的整体描述,是一个模板,是对象的抽象,是具有相同行为和特征的对象的描述 对象具体的一个事物,是类的实例化,万事万物皆对象 ...
- 【Java】Java 高频面试题英语版(1)
今天分享 Java 高频面试题英语版.音频文件放在下方,点击获取. [Java]Java 高频面试题英语版(1) [Java]Java 高频面试题英语版(2) [Java]Java 高频面试题英语 ...
- 100道Java高频面试题(阿里面试官整理)
我分享文章的时候,有个读者回复说他去年就关注了我的微信公众号,打算看完我的所有文章,然后去面试,结果我后来很长时间不更新了...所以为了弥补一直等我的娃儿们,给大家的金三银四准备了100道花时间准备的 ...
- java高频面试题(2023最新)
目录 一.java基础 1.八大基础类型 2.java三大特性 3.重载和重写的区别 4.pubilc.protected.(dafault)不写.private修饰符的作用范围 5.==和equal ...
- Java高频面试题汇总(2022)
Java 1. ArrayList和LinkedList的区别 Array(数组)是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的. Array获取数据的时间复杂度是O(1 ...
- Java高频面试题总结
本文目录: Java的特点 Java 与 C++ 的区别 面向对象和面向过程的区别? JKD和JRE的区别? 面向对象有哪些特性? Java的基本数据类型有哪些? 什么是值传递和引用传递? 自动装箱和 ...
- 五面拿下阿里飞猪offer,熬夜整理Java高频面试题
前言 Spring如何解决的循环依赖,是近两年流行起来的一道Java面试题.其实笔者本人对这类框架源码题还是持一定的怀疑态度的.如果笔者作为面试官,可能会问一些诸如"如果注入的属性为null ...
- 腾讯+阿里+百度Java高频面试题(涵盖了年薪20W80W的高频面试题)
性能优化面试专栏 tomcat性能优化整理 1.你怎样给tomcat调优 2.如何加大comcat连接数 3.怎样加大tomcat的内存 4.tomcat中如何禁止列目录下的文件 5.Tomcat有几 ...
最新文章
- 设计模式:单例模式在JDK中的应用
- 免费OA系统品牌有那些?
- IOI1999 花店橱窗布置
- angular5.0封装underscore常用pipe并发布到npm全套流程
- 求圆面积的python代码_《求》字意思读音、组词解释及笔画数 - 新华字典 - 911查询...
- .net 链oracle,.net链数据库oracle
- 生成内核版本号头文件的方法
- textContent和innerText属性的区别
- thinkPHP 表单自动验证功能
- html 时间控件 只选择年,js时间控件只显示年月
- 访问局域网计算机切换用户,Win7切换用户账户访问共享文件夹的方法
- laravel框架中hasOne和blongTo的用法详解
- Rhythmbox中mp3中文乱码解决
- Lecture 8:Norms of Vectors and Matrices
- python摇骰子游戏小案例
- 2022网鼎杯青龙组wp
- Android安装busybox
- 苏州大学2021年全日制博士学位研究生招生简章
- [置顶] 忆往昔,看今朝(2012-2013年总结)
- 前端入门练习之将psd文件转换为HTML文件
热门文章
- 《Essential C++》笔记之关联容器set的使用总结
- html界面选择按钮没法取消,如何使用JavaScript取消选择按钮
- java处理超大csv文件_比较 csv 文件中数据差异
- 怀旧服小号最多的服务器,魔兽世界怀旧服小号战场将成为GZS量产高督的基地?...
- linux test 使用方法,Linux系统test命令使用方法介绍
- file对象怎样获取文件的长度?_使用FSO对象获取整个文件夹的信息
- 如何提取html的文本,如何从html标签之间提取文本?
- linux socket编程web服务器实现报文解析,[Socket][网络编程]程序范例:Linux下连接WEB服务器...
- hadoop 配置 docker伪分布式(单节点)
- pytorch nn.LogSoftmax