實現手機直播原始碼中兩個執行緒依次執行的相關程式碼

雲豹科技程式設計師發表於2021-12-17
雖然通常在手機直播原始碼中每個子執行緒只需要完成自己的任務,但是有時我們希望多個執行緒一起工作來完成一個任務,這就涉及到執行緒間通訊。今天我們主要來了解一下實現手機直播原始碼中兩個執行緒依次執行的相關程式碼。
假設手機直播原始碼中有兩個執行緒:A 和 B,這兩個執行緒都可以按照順序列印數字,程式碼如下:
public class Test01 {
    public static void main(String[] args) throws InterruptedException {
        demo1();
    }
    public static void demo1() {
        Thread a = new Thread(() -> {
            printNumber("A");
        });
        Thread b = new Thread(() -> {
            printNumber("B");
        });
        a.start();
        b.start();
    }
    public static void printNumber(String threadName) {
        int i = 0;
        while (i++ < 3) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(threadName + " print: " + i);
        }
    }
}
得到的結果如下:
A print: 1
B print: 1
B print: 2
A print: 2
A print: 3
B print: 3
可以看到 A 和 B 同時列印數字,如果我們希望 B 在 A 執行完成之後開始執行,那麼可以使用 thread.join() 方法實現,程式碼如下:
public static void demo2() {
    Thread a = new Thread(() -> {
        printNumber("A");
    });
    Thread b = new Thread(() -> {
        System.out.println("B 等待 A 執行");
        try {
            a.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        printNumber("B");
    });
    a.start();
    b.start();
}
得到的結果如下:
B 等待 A 執行
A print: 1
A print: 2
A print: 3
B print: 1
B print: 2
B print: 3
我們可以看到該 a.join() 方法會讓 B 等待 A 完成列印。
thread.join() 方法的作用就是阻塞當前執行緒,等待呼叫 join() 方法的執行緒執行完畢後再執行後面的程式碼。
檢視 join() 方法的原始碼,內部是呼叫了 join(0) ,如下:
public final void join() throws InterruptedException {
    join(0);
}
檢視 join(0) 的原始碼如下:
// 注意這裡使用了 sychronized 加鎖,鎖物件是執行緒的例項物件
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");
    }
    // 呼叫 join(0) 執行下面的程式碼
    if (millis == 0) {
        // 這裡使用 while 迴圈的目的是為了避免虛假喚醒
        // 如果當前執行緒存活則呼叫 wait(0), 0 表示永久等待,直到呼叫 notifyAll() 或者 notify() 方法
        // 當執行緒結束的時候會呼叫 notifyAll() 方法
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
從原始碼中可以看出 join(long millis) 方法是通過 wait(long timeout) (Object 提供的方法)方法實現的,呼叫 wait 方法之前,手機直播原始碼當前執行緒必須獲得物件的鎖,所以此 join 方法使用了 synchronized 加鎖,鎖物件是執行緒的例項物件。 其中 wait(0)方法會讓當前執行緒阻塞等待,直到另一個執行緒呼叫此物件的 notify() 或者 notifyAll() 方法才會繼續執行。當呼叫 join 方法的執行緒結束的時候會呼叫 notifyAll() 方法,所以 join() 方法可以實現手機直播原始碼中一個執行緒等待另一個呼叫 join() 的執行緒結束後再執行。
虛假喚醒:一個執行緒在沒有被通知、中斷、超時的情況下被喚醒;
虛假喚醒可能導致條件不成立的情況下手機直播原始碼執行程式碼,破壞被鎖保護的約束關係;
為什麼使用 while 迴圈來避免虛假喚醒:
在 if 塊中使用 wait 方法,是非常危險的,因為一旦手機直播原始碼中的執行緒被喚醒,並得到鎖,就不會再判斷 if 條件而執行 if 語句塊外的程式碼,所以建議凡是先要做條件判斷,再 wait 的地方,都使用 while 迴圈來做,迴圈會在等待之前和之後對條件進行測試。
本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理 原文連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2848343/,如需轉載,請註明出處,否則將追究法律責任。

相關文章