等待this.$confirm执行完_很好玩的多线程面试题:如何保证线程顺序执行?两个实例讲清楚...

本文介绍了如何保证多线程T1、T2、T3按照特定顺序执行,通过分析`join()`方法的使用和一个交替打印ABC的示例,详细解释了利用`wait()`和`notify()`实现线程同步的机制。
摘要由CSDN通过智能技术生成

作为面试宠儿的多线程,在面试的时候是一定会被询问的话题,今天,在和朋友聊天的时候,他问了我一道很好玩的多线程面试题,不难,但是想解释清楚,还真的是不容易

问题:现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行

当看到这个问题的时候,我的第一反应就是wait、notify(会在后面附上代码+解释),然后脑子里闪过好多不同的方案,那我们就来看一下我的第一反应是如何处理的

分析:

  1. Thread类中的join方法是用来同步的,底层其实是调用了 wait方法。先来看一下演示代码:

package com.whh.concurrency;
/**
*@description:
* 问题:现在有 T1、T2、T3 三个线程,怎样保证 T2 在 T1 执行完后执行T3在T2执行完
* 分析:使用join方法实现
*@author:wenhuohuo
*/
public class MyJoin {
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1");
}
},"t1");
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程2");
}
},"t2");
final Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("线程3");
}
},"t3");
t3.start();
t2.start();
t1.start();
}
}

执行结果:

线程1
线程2
线程3

可以看到,我们让t2线程调用t1.join,t3调用t2.join,尽管是t3,t2,t1分别start,执行顺序还是t1,t2,t3。是因为join方法底层使用的是wait方法。

  1. 查看join方法源码

public final void join() throws InterruptedException {
join(0); //传入的是毫秒值
}
public final synchronized void join(long millis)throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) { //isAlive()是native方法,判断线程是否还存活
wait(0); //wait(0),不计时间,一直等待,直到有notify()或notifyAll()来唤醒。
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);//传入时间,表示在时间值消耗完之前一直等待,直到过了等待时间。
now = System.currentTimeMillis() - base;
}
}
}

1)从源码中,我们结合之前的代码分析,t2.join()和t3.join(),均没有传值,相当于join(0),表示不计时间,t2会一直wait等待t1执行完成,t3会一直wait等待t2执行完成。所以执行结果顺序是t3,t2,t1。

2)当传入的毫秒值不为0时,就一直循环等待,直到过了等待时间(dalay<=0),则执行break方法,那么将不再等待。

  1. 改变join()传入的毫秒值,查看执行顺序并分析结果:

public class MyJoin {
public static void main(String[] args) {
final Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//处理业务时间,模拟为8秒
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1");
}
},"t1");
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join(4000); //t2等待t1线程4秒
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程2");
}
},"t2");
final Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join(2000); //t3等待t2线程2秒
}catch (Exception e){
e.printStackTrace();
}
System.out.println("线程3");
}
},"t3");
t3.start();
t2.start();
t1.start();
}
}

执行结果:

线程3	//程序启动过了2秒执行t3
线程2 //过了4秒执行t2
线程1 //过了8秒执行t1

分析:我们让t1 睡眠8秒模拟业务执行时间,t2等待t1 的时间为4秒,t3等待t2的时间为2秒。那么当t1,t2,t3启动后,等待的时间,t3会因为t2的等待时间4秒太长而先与t2执行,t2会因为t1的8秒太长而先与t1执行。


好了,到这里不知道大家感觉怎么样,有没有想明白,其实,关于多线程顺序执行的问题,我总结了一个万能的解答方法,通过一个小例子进行演示

例题描述:建立三个线程,A线程打印100次A,B线程打印100次B,C线程打印100次C,要求线程同时运行,交替打印100次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。

代码如下:

public class QueueThread implements Runnable{

private Object current;
private Object next;
private int max=100;
private String word;

public QueueThread(Object current, Object next, String word) {
this.current = current;
this.next = next;
this.word = word;
}

@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i synchronized (current) {
synchronized (next) {
System.out.println(word);<span id="transmark">span>
next.notify();
}
try {
current.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//必须做一下这样处理,否则thread1-thread4停不了
synchronized (next) {
next.notify();
System.out.println(Thread.currentThread().getName()+"执行完毕");
}

}

public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
Object a = new Object();
Object b = new Object();
Object c = new Object();
Object d = new Object();
Object e = new Object();
//之所以每次当前线程都要sleep(10)是为了保证线程的执行顺序
new Thread(new QueueThread(a,b,"a")).start();
Thread.sleep(10);
new Thread(new QueueThread(b,c,"b")).start();
Thread.sleep(10);
new Thread(new QueueThread(c,d,"c")).start();
Thread.sleep(10);
new Thread(new QueueThread(d,e,"d")).start();
Thread.sleep(10);
Thread thread4 = new Thread(new QueueThread(e,a,"e"));
thread4.start();
thread4.join();//因为线程0-4停止是依次执行的,所以如果保证主线程在线程4后停止,那么就能保证主线程是最后关闭的
System.out.println("程序耗时:"+ (System.currentTimeMillis()-startTime ));

}
}

其实这个程序很容易理解,首先,我们保证了线程0-线程4依次启动,并设置了Thread.sleep(10),保证线程0-4依次执行他们的run方法。

其次,我们看QueueThread的run()便可知:1.线程获得current锁,2.获得next锁。3.打印并notify拥有next锁的一个对象4.线程执行current.wait(),释放current锁对象,并使线程处于阻塞状态。

然后,假设已经执行到了thread-4的run方法,那么此时的情况是这样的:

线程0处于阻塞状态,需要a.notify()才能使其回到runnale状态

线程1处于阻塞状态,需要b.notify()才能使其回到runnale状态

线程2处于阻塞状态,需要c.notify()才能使其回到runnale状态

线程3处于阻塞状态,需要d.notify()才能使其回到runnale状态

而线程4恰好可以需要执行a.notify(),所以能够使线程0回到runnale状态。然后执行e,wait()方法,使自身线程阻塞,需要e.notify()才能唤醒。

依次执行下去,就可以发现规律了!

最后之所以要在for循环后加上一个处理,是因为,如果不进行处理,除了线程0能够结束for循环,其余线程1-4实际上是会停在current,wait()这句代码的。

假设已经执行到最后一次循环了,此时线程4唤醒线程0,并将自身阻塞。线程0被唤醒后,继续执行,然而因为i=max的缘故,它无法再进入循环了。然而如果循环后没有唤醒下一个线程的操作的话,那么剩下的线程1-4就会一直处于阻塞状态!也就不会停止了。但是加了处理之后就完美解决了。

好啦,到这里两个小实例就讲完了,不知道通过这两个小实例有没有让你很好的理解这道面试题呢?有问题的话,下方评论区,大家一起讨论吧

欢迎大家关注我,一个脑回路清奇的程序猿,在娱乐中学习是我的真谛,大家一起努力进步

b585fc4af0f5c8ef6bc4f2a29b10a571.png— END—

关注作者微信公众号 —《Java架构师联盟》

了解更多java后端架构知识以及最新面试宝典

fc06e6483094778f17e83960cdc6a17f.png

26551796fef38d878c1ba4a6412ecea2.png你点的每个好看,我都认真当成了 15233b945cda6c5ce923e62085c188e1.png

看完本文记得给作者点赞+在看哦~~~大家的支持,是作者远远不断出文的动力。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值