1. 程式人生 > >顛覆我的Thread.join()

顛覆我的Thread.join()

hello 喚醒 [] row 之前 world! 優先 tar 對象

學而時習之,不亦說乎!

--《論語》

  為什麽說是顛覆?

  1)任何對象都可以作為鎖對象,鎖對象的行為都是一樣的嗎?之前我一直認為鎖對象的方法都是定義在Object類中,而所有類都是Object的子類,這些方法又都是native方法,那麽用哪個對象作為鎖對象又有什麽區別呢?

  2)一個線程對象a在run()方法內部調用線程對象b的join()方法,那麽是將b線程加入,等到b線程執行完畢再執行a線程?那麽如果還有一個正在執行的c線程呢,線程c也會等待b執行完嗎?

代碼1:

package com.zby;

public class
Application1 { public static void main(String[] args) { Thread prepare = new Thread(new Runnable() { public void run() { for (int i = 0; i < 5; i++) { System.out.println("Hello,World!-----" + i); try { Thread.sleep(
500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); prepare.start(); System.out.println("Hello,ZBY!"); } }

控制臺輸出:

Hello,ZBY!
Hello,World!-----0
Hello,World!-----1
Hello,World
!-----2 Hello,World!-----3 Hello,World!-----4

結果不難分析,主線程直接執行,而prepare線程雖然啟動了,但是執行沒那麽快,所以後執行了。但是我的prepare是進行準備工作的,我想讓prepare線程執行完畢後再執行主線程。

代碼2:

package com.zby;

public class Application2 {

    public static void main(String[] args) {
        Thread prepare = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Hello,World!-----" + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        prepare.start();
        try {
            prepare.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Hello,ZBY!");
    }
}

控制臺輸出:

Hello,World!-----0
Hello,World!-----1
Hello,World!-----2
Hello,World!-----3
Hello,World!-----4
Hello,ZBY!

很小兒科,加了一個一行代碼:prepare.join();要是之前我會理解成把prepare加入到主線程先執行,執行完才能執行其它線程。然而,非也。

Thread.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()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

這兒可以看出來,我們調用prepare.join()的時候發生了什麽:把prepare作為鎖對象,調用鎖對象的wait(0)方法,阻塞當前線程!也就是說,並不是把需要執行的線程加入進來讓他先執行,而是阻塞當前的線程!那麽,如果還有第三個線程也在執行,那麽prepare線程是不會一直執行,而是跟第三個線程搶CPU執行權。總結起來就是,其實就是阻塞了當前的線程,對於其他線程都是沒有影響的。感興趣可以加入第三個線程自己測試一下。

等價於代碼2的代碼3:

package com.zby;

public class Application3 {

    public static void main(String[] args) {
        Thread prepare = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Hello,World!-----" + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        prepare.start();
        synchronized(prepare){
            try {
                prepare.wait(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Hello,ZBY!");
    }
}

控制臺輸出:

Hello,World!-----0
Hello,World!-----1
Hello,World!-----2
Hello,World!-----3
Hello,World!-----4
Hello,ZBY!

這兒就解決了第二個問題,join()方法其實不是把調用的線程加入進來優先執行,而是阻塞當前線程!

看完代碼3就有疑問了,prepare.wait(0);自然沒錯,阻塞住了主線程。但是並沒有任何地方調用notify或者notifyAll方法,線程不是應該一直阻塞麽,怎麽會在prepare執行完後繼續執行主線程代碼了?

這就是第一個問題了,普通對象當然是wait後必須等待notify喚醒才能繼續執行,但是Thread對象呢?具體的我也不知道,但是從這兒可以推論出,thread對象在執行完畢後,自動喚醒了!那麽到底是notify還是notifyAll呢?那麽,多啟動一個線程,並使用prepare對象作為鎖對象,調用wait方法。

代碼4:

package com.zby;

public class Application3 {

    public static void main(String[] args) {
        final Thread prepare = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("Hello,World!-----" + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        prepare.start();
        Thread ready = new Thread(new Runnable() {
            public void run() {
                synchronized (prepare) {
                    try {
                        prepare.wait(0);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                for (int i = 0; i < 10; i++) {
                    System.out.println("Hello,Earth!-----" + i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        ready.start();
        synchronized (prepare) {
            try {
                prepare.wait(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Hello,ZBY!");
    }
}

控制臺輸出:

Hello,World!-----0
Hello,World!-----1
Hello,World!-----2
Hello,World!-----3
Hello,World!-----4
Hello,Earth!-----0
Hello,ZBY!
Hello,Earth!-----1
Hello,Earth!-----2
Hello,Earth!-----3
Hello,Earth!-----4
Hello,Earth!-----5
Hello,Earth!-----6
Hello,Earth!-----7
Hello,Earth!-----8
Hello,Earth!-----9

可以看出,在preare執行完之後,自動把ready和main線程都喚醒了!也就是說,使用Thread對象作為鎖對象,在Thread執行完成之後,會喚醒使用該對象作為鎖對象調用wait()休眠的線程。

總結:

1)使用線程的join方法,是將調用的線程對象作為鎖對象,阻塞當前線程,不影響其他線程的運行。

2)推論:Thread對象作為線程鎖對象,會在Thread對象執行完後,調用Thread對象的notifyAll方法。

顛覆我的Thread.join()