• 【高并发】深入理解线程的执行顺序


    大家好,我是冰河~~

    最近经常有读者问我:冰河,线程到底是按照怎样的顺序执行的呀?为了同一回答大家的这个问题,今天我就单独写一篇文章吧。好了,不多说了,进入今天的正题。

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

    调用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方法,结果如下所示。

    thread1
    thread2
    thread3
    

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

    thread1
    thread3
    thread2
    

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

    thread2
    thread3
    thread1
    

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

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

    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();
        }
    }
    

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

    thread1
    thread2
    thread3
    

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

    thread1
    thread2
    thread3
    

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

    thread1
    thread2
    thread3
    

    可以看到,每次运行的结果都是相同的,所以,使用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()方法会继续向下执行,启动第二个子线程,并执行子线程的业务逻辑,以此类推。

  • 相关阅读:
    eclipse中编译出现错误undefined reference to `_sbrk'
    STM32L431驱动带UC1698芯片调试记录
    IAR里面STM32工程使用printf
    STM32L431仿真卡在HAL_InitTick(TICK_INT_PRIORITY);
    电信NB-IOT的温湿度采集器开发记录
    程序运行之ELF文件的段
    linux c编程:进程控制(二)_竞争条件
    ubuntun下安装Fiddler
    程序运行之目标文件(一)
    linux c编程:进程控制(一)
  • 原文地址:https://www.cnblogs.com/binghe001/p/15815009.html
Copyright © 2020-2023  润新知