天天看點

注意:線程的執行順序與你想的并不一樣!!

大家好,我是冰河~~

今天,我們一起來講講線程的執行順序,可能與你想象的不一樣,全程高能,一起加油~~

一、線程的執行順序是不确定的

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

可以看到,ThreadSort02類比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()方法會繼續向下執行,啟動第二個子線程,并執行子線程的業務邏輯,以此類推。

好了,今天就到這兒吧,我是冰河,我們下期見~~