写点什么

【高并发】如何确保线程按照我们想要的顺序执行?

用户头像
冰河
关注
发布于: 22 小时前
【高并发】如何确保线程按照我们想要的顺序执行?

大家好,我是冰河~~


今天,我们继续聊【高并发】系列的话题,今天,我们来聊聊如何确保线程按照我们想要的顺序执行。


好了,开始今天的正文吧。

一、线程的执行顺序是不确定的

调用 Thread 的 start()方法启动线程时,线程的执行顺序是不确定的。也就是说,在同一个方法中,连续创建多个线程后,调用线程的 start()方法的顺序并不能决定线程的执行顺序。


例如,这里,看一个简单的示例程序,如下所示。


package io.binghe.concurrent.lab03;
/** * @author binghe * @version 1.0.0 * @description 线程的顺序,直接调用Thread.start()方法执行不能确保线程的执行顺序 */public class ThreadSort01 { public static void main(String[] args){ Thread thread1 = new Thread(() -> { System.out.println("thread1"); }); Thread thread2 = new Thread(() -> { System.out.println("thread2"); }); Thread thread3 = new Thread(() -> { System.out.println("thread3"); });
thread1.start(); thread2.start(); thread3.start(); }}
复制代码


在 ThreadSort01 类中分别创建了三个不同的线程,thread1、thread2 和 thread3,接下来,在程序中按照顺序分别调用 thread1.start()、thread2.start()和 thread3.start()方法来分别启动三个不同的线程。


那么,问题来了,线程的执行顺序是否按照 thread1、thread2 和 thread3 的顺序执行呢?运行 ThreadSort01 的 main 方法,结果如下所示。


thread1thread2thread3
复制代码


再次运行时,结果如下所示。


thread1thread3thread2
复制代码


第三次运行时,结果如下所示。


thread2thread3thread1
复制代码


可以看到,每次运行程序时,线程的执行顺序可能不同。线程的启动顺序并不能决定线程的执行顺序。

二、如何确保线程的执行顺序

1.确保线程执行顺序的简单示例

在实际业务场景中,有时,后启动的线程可能需要依赖先启动的线程执行完成才能正确的执行线程中的业务逻辑。此时,就需要确保线程的执行顺序。那么如何确保线程的执行顺序呢?


可以使用 Thread 类中的 join()方法来确保线程的执行顺序。例如,下面的测试代码。


package io.binghe.concurrent.lab03;
/** * @author binghe * @version 1.0.0 * @description 线程的顺序,Thread.join()方法能够确保线程的执行顺序 */public class ThreadSort02 { public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> { System.out.println("thread1"); }); Thread thread2 = new Thread(() -> { System.out.println("thread2"); }); Thread thread3 = new Thread(() -> { System.out.println("thread3"); });
thread1.start();
//实际上让主线程等待子线程执行完成 thread1.join();
thread2.start(); thread2.join();
thread3.start(); thread3.join(); }}
复制代码


可以看到,ThreadSort02 类比 ThreadSort01 类,在每个线程的启动方法下面添加了调用线程的 join()方法。此时,运行 ThreadSort02 类,结果如下所示。


thread1thread2thread3
复制代码


再次运行时,结果如下所示。


thread1thread2thread3
复制代码


第三次运行时,结果如下所示。


thread1thread2thread3
复制代码


可以看到,每次运行的结果都是相同的,所以,使用 Thread 的 join()方法能够保证线程的先后执行顺序。

2.join 方法如何确保线程的执行顺序

既然 Thread 类的 join()方法能够确保线程的执行顺序,我们就一起来看看 Thread 类的 join()方法到底是个什么鬼。


进入 Thread 的 join()方法,如下所示。


public final void join() throws InterruptedException {    join(0);}
复制代码


可以看到 join()方法调用同类中的一个有参 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()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } }}
复制代码


可以看到,有一个 long 类型参数的 join()方法使用了 synchroinzed 修饰,说明这个方法同一时刻只能被一个实例或者方法调用。由于,传递的参数为 0,所以,程序会进入如下代码逻辑。


if (millis == 0) {  while (isAlive()) {    wait(0);  }}
复制代码


首先,在代码中以 while 循环的方式来判断当前线程是否已经启动处于活跃状态,如果已经启动处于活跃状态,则调用同类中的 wait()方法,并传递参数 0。继续跟进 wait()方法,如下所示。


public final native void wait(long timeout) throws InterruptedException;
复制代码


可以看到,wait()方法是一个本地方法,通过 JNI 的方式调用 JDK 底层的方法来使线程等待执行完成。


需要注意的是,调用线程的 wait()方法时,会使主线程处于等待状态,等待子线程执行完成后再次向下执行。也就是说,在 ThreadSort02 类的 main()方法中,调用子线程的 join()方法,会阻塞 main()方法的执行,当子线程执行完成后,main()方法会继续向下执行,启动第二个子线程,并执行子线程的业务逻辑,以此类推。


好了,今天就到这儿吧,我是冰河,我们下期见~~

发布于: 22 小时前阅读数: 12
用户头像

冰河

关注

公众号:冰河技术 2020.05.29 加入

互联网高级技术专家,《深入理解分布式事务:原理与实战》,《海量数据处理与大数据技术实战》和《MySQL技术大全:开发、优化与运维实战》作者,mykit-data与mykit-transaction-message框架作者。【冰河技术】作者。

评论

发布
暂无评论
【高并发】如何确保线程按照我们想要的顺序执行?