java百度面试题_一道百度java面试题的多种解法

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

java程序,主进程需要等待多个子进程结束之后再执行后续的代码,有哪些方案可以实现?

这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个处理都可以用一个线程来执行,所有处理完成了之后才会返回给用户下单成功,欢迎大家批评指正:

1.join方法

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

示例:

1 importjava.util.Vector;2

3 public classTest {4 public static void main(String[] args) throwsInterruptedException {5 Vector vector = new Vector<>();6 for(int i=0;i<5;i++) {7 Thread childThread= new Thread(newRunnable() {8

9 @Override10 public voidrun() {11 //TODO Auto-generated method stub

12 try{13 Thread.sleep(1000);14 } catch(InterruptedException e) {15 //TODO Auto-generated catch block

16 e.printStackTrace();17 }18 System.out.println("子线程被执行");19 }20

21 });22 vector.add(childThread);23 childThread.start();24 }25 for(Thread thread : vector) {26 thread.join();27 }28 System.out.println("主线程被执行");29 }

执行结果:

子线程被执行

子线程被执行

子线程被执行

子线程被执行

子线程被执行

主线程被执行

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使用完毕后,它不能再次被使用。

1 importjava.util.Vector;2 importjava.util.concurrent.CountDownLatch;3

4 public classTest2 {5 public static void main(String[] args) throwsInterruptedException {6 final CountDownLatch latch = new CountDownLatch(5);7 for(int i=0;i<5;i++) {8 Thread childThread= new Thread(newRunnable() {9

10 @Override11 public voidrun() {12 //TODO Auto-generated method stub

13 try{14 Thread.sleep(1000);15 } catch(InterruptedException e) {16 //TODO Auto-generated catch block

17 e.printStackTrace();18 }19 System.out.println("子线程被执行");20 latch.countDown();21 }22

23 });24

25 childThread.start();26

27 }28 latch.await();//阻塞当前线程直到latch中的值

29 System.out.println("主线程被执行");30 }31

32 }

执行结果:

子线程被执行

子线程被执行

子线程被执行

子线程被执行

子线程被执行

主线程被执行

3.同步屏障CyclicBarrier

这里必须注意,CylicBarrier是控制一组线程的同步,初始化的参数:5的含义是包括主线程在内有5个线程,所以只能有四个子线程,这与CountDownLatch是不一样的。

countDownLatch和cyclicBarrier有什么区别呢,他们的区别:countDownLatch只能使用一次,而CyclicBarrier方法可以使用reset()方法重置,所以CyclicBarrier方法可以能处理更为复杂的业务场景。

我曾经在网上看到一个关于countDownLatch和cyclicBarrier的形象比喻,就是在百米赛跑的比赛中若使用 countDownLatch的话冲过终点线一个人就给评委发送一个人的成绩,10个人比赛发送10次,如果用CyclicBarrier,则只在最后一个人冲过终点线的时候发送所有人的数据,仅仅发送一次,这就是区别。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 packageinterview;2

3 importjava.util.concurrent.BrokenBarrierException;4 importjava.util.concurrent.CyclicBarrier;5

6 public classTest3 {7 public static void main(String[] args) throwsInterruptedException, BrokenBarrierException {8 final CyclicBarrier barrier = new CyclicBarrier(5);9 for(int i=0;i<4;i++) {10 Thread childThread= new Thread(newRunnable() {11

12 @Override13 public voidrun() {14 //TODO Auto-generated method stub

15 try{16 Thread.sleep(1000);17 } catch(InterruptedException e) {18 //TODO Auto-generated catch block

19 e.printStackTrace();20 }21 System.out.println("子线程被执行");22 try{23 barrier.await();24 } catch(InterruptedException e) {25 //TODO Auto-generated catch block

26 e.printStackTrace();27 } catch(BrokenBarrierException e) {28 //TODO Auto-generated catch block

29 e.printStackTrace();30 }31 }32

33 });34

35 childThread.start();36

37 }38 barrier.await();//阻塞当前线程直到latch中的值

39 System.out.println("主线程被执行");40 }41 }

View Code

执行结果:

子线程被执行

子线程被执行

子线程被执行

子线程被执行

主线程被执行

4.使用yield方法(注意此种方法经过亲自试验证明并不可靠!)

1 public classTest4 {2 public static void main(String[] args) throwsInterruptedException {3 for(int i=0;i<5;i++) {4 Thread childThread= new Thread(newRunnable() {5

6 @Override7 public voidrun() {8 //TODO Auto-generated method stub

9 try{10 Thread.sleep(1000);11 } catch(InterruptedException e) {12 //TODO Auto-generated catch block

13 e.printStackTrace();14 }15 System.out.println("子线程被执行");16

17 }18

19 });20

21 childThread.start();22

23 }24 while (Thread.activeCount() > 2) { //保证前面的线程都执行完

25 Thread.yield();26 }27 System.out.println("主线程被执行");28 }29 }

执行结果:

1 子线程被执行2 子线程被执行3 子线程被执行4 子线程被执行5 主线程被执行6 子线程被执行

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

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

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

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

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

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

1 importjava.util.concurrent.Callable;2 importjava.util.concurrent.ExecutionException;3 importjava.util.concurrent.FutureTask;4

5 public classTest5 {6 public static voidmain(String[] args) {7 MyThread td = newMyThread();8

9 //1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。

10 FutureTask result1 = new FutureTask<>(td);11 newThread(result1).start();12 FutureTask result2 = new FutureTask<>(td);13 newThread(result2).start();14 FutureTask result3 = new FutureTask<>(td);15 newThread(result3).start();16

17 Integer sum;18 try{19 sum =result1.get();20 sum =result2.get();21 sum =result3.get();22 //这里获取三个sum值只是为了同步,并没有实际意义

23 System.out.println(sum);24 } catch(InterruptedException e) {25 //TODO Auto-generated catch block

26 e.printStackTrace();27 } catch(ExecutionException e) {28 //TODO Auto-generated catch block

29 e.printStackTrace();30 } //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的

31

32 System.out.println("主线程被执行");33

34 }35

36 }37

38 class MyThread implements Callable{39

40 @Override41 public Integer call() throwsException {42 int sum = 0;43 Thread.sleep(1000);44 for (int i = 0; i <= 10; i++) {45 sum +=i;46 }47 System.out.println("子线程被执行");48 returnsum;49 }50 }

6.使用callable+future

Callable+Future最终也是以Callable+FutureTask的形式实现的。

在这种方式中调用了: Future future = executor.submit(task);

1 importjava.util.concurrent.Callable;2 importjava.util.concurrent.ExecutionException;3 importjava.util.concurrent.ExecutorService;4 importjava.util.concurrent.Executors;5 importjava.util.concurrent.Future;6

7 public classTest6 {8 public static void main(String[] args) throwsInterruptedException, ExecutionException {9 ExecutorService executor =Executors.newCachedThreadPool();10 Task task = newTask();11 Future future1 =executor.submit(task);12 Future future2 =executor.submit(task);13 //获取线程执行结果,用来同步

14 Integer result1 =future1.get();15 Integer result2 =future2.get();16

17 System.out.println("主线程执行");18 executor.shutdown();19 }20 }21 class Task implements Callable{22 @Override public Integer call() throwsException {23 int sum = 0;24 //do something;

25 System.out.println("子线程被执行");26 returnsum;27 }28 }

执行结果:

子线程被执行

子线程被执行

主线程执行

补充:

1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:

CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;

而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

CountDownLatch类实际上是使用计数器的方式去控制的,不难想象当我们初始化CountDownLatch的时候传入了一个int变量这个时候在类的内部初始化一个int的变量,每当我们调用countDownt()方法的时候就使得这个变量的值减1,而对于await()方法则去判断这个int的变量的值是否为0,是则表示所有的操作都已经完成,否则继续等待。

实际上如果了解AQS的话应该很容易想到可以使用AQS的共享式获取同步状态的方式来完成这个功能。而CountDownLatch实际上也就是这么做的。

参考文献:

https://blog.csdn.net/u011277123/article/details/54015755/

https://blog.csdn.net/joenqc/article/details/76794356

https://blog.csdn.net/weixin_38553453/article/details/72921797

https://blog.csdn.net/LightOfMiracle/article/details/73456832

https://www.cnblogs.com/baizhanshi/p/6425209.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值