java 多線程基礎(三)

等待(wait)和通知(notify)

    爲了支持多線程之間的協做,JDK提供了兩個很是重要的接口線程等待wait()方法和通知notify()方法。這兩個方法並不在Thread類中,而是輸出Object類。這也就意味着任何對象均可以調用這兩個方法。java

public final void wait() throws InterruptedException {wait(0);}
public final native void notify();

    當在一個對象實例上調用wait()方法後,當前線程就會在這個對象上等待。這是什麼意思呢?好比線程A中,調用了obj.wait()方法,那麼線程A就會中止繼續執行,而轉爲等待狀態。等待到什麼時候結束呢?線程A會一直等到其餘線程調用了obj.notify()方法爲止。這時obj對象就儼然成爲多個線程之間的有效通信手段。多線程

    這裏還須要強調一點,Object.wait()方法並非能夠隨便調用。它必須包含在對應的synchronzied語句中,而且都須要先得到目標對象的一個監視器。顯示了wait()與notify()的工做流程細節。其中T1和T2表示兩個線程。T1在正確執行wait()方法前,首先必須得到object對象的監視器,執行後會釋放這個監視器。爲了方便你們理解,這裏給出一個簡單的例子:ide

                                       

public class ThreadTest3 {
    final static Object obj = new Object();
    public static class T1 extends Thread{
        public void run(){
            synchronized (obj){
                System.out.println(System.currentTimeMillis()+":T1 Start!");
                try {
                    System.out.println(System.currentTimeMillis()+":T1 wait for object");
                    obj.wait();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis()+":T1 end");
            }
        }
    }
    public static class T2 extends Thread{
        public void run(){
            synchronized (obj){
                System.out.println(System.currentTimeMillis()+":T2 Start!");
                System.out.println(System.currentTimeMillis()+":T2 wait for object");
                obj.notify();
                System.out.println(System.currentTimeMillis()+":T2 end");
                try {
                    Thread.sleep(20000);
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) {
        Thread t1 = new T1();
        Thread t2 = new T2();
        t1.start();
        t2.start();
    }
}

執行結果以下所示:spa

1496717793778:T1 Start!
1496717793778:T1 wait for object
1496717793779:T2 Start!
1496717793779:T2 wait for object
1496717793779:T2 end
1496717813779:T1 end

    從程序打印的時間能夠到出,在T2通知T1繼續執行後,T1並不能當即繼續執行,而是要等待T2釋放obj的鎖,並從新成功獲取鎖後,才能繼續執行。所以T2休息了20秒。線程

    注意:object.wait()和Thread.sleep()方法均可以讓線程等待若干時間,除了wait()能夠被喚醒外,另一個主要區別就是wait()方法會釋放目標對象鎖,而是Thread.sleep()方法不會釋聽任何資源。接下來要介紹code

的suspend也不會釋聽任何鎖資源對象

掛起(suspend)和繼續執行(resume)

    不推薦使用suspend()去掛起線程的緣由,是由於suspend()在致使縣城暫停的同時,並不會釋聽任何鎖資源。此時,其餘線程想要訪問被它暫用的鎖時,都會沒法正常繼續運行。直到對應的線程進行了resume()操做,被掛起的線程才能繼續執行。在此不進行詳細描述。接口

等待線程結束(join)和謙讓(yield)

    隔壁的一時候,一個線程的輸入可能很是依賴於另一個或是多個線程的輸出,此時,這個線程就須要等待依賴線程執行完畢,才能繼續執行。JDK提供了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;
        }
    }
}

給出一個簡單點join()實例,給你們參考虛擬機

public class ThreadTestJoin {
    public volatile static int i=0;
    public static class addThread extends Thread{
        public void run(){
            for (i = 0; i < 1000000; i++);
        }
    }
    public static void main(String[] args) throws InterruptedException {
        addThread thread = new addThread();
        thread.start();
        System.out.println("等待前的:i"+i);
        thread.join();
        System.out.println("等待後的:i"+i);
    }
}
0
1000000

從上能夠看出,join()沒有等待addThread,i極可能是0或是一個很是小的數字。由於addThread尚未開始執行i的值就已經被輸出了。可是使用join()方法後,標識主線程願意等待addThread執行完畢,故join()返回時,addThread已經執行完畢,故i老是1000000。

    另一個比較有趣的方法yield(),定義以下:

public static native void yield();

    這是一個靜態方法,一旦執行,他會使當前線程讓出CPU。但要注意,讓出CPU並不表示當前線程不執行。當前線程在讓出CPU後,還會進行CPU資源的爭奪。可是是否可以再次被分配到,就不必定了。所以對yield()的調用就好像在說:我已經完成一些重要的工做了,我應該是能夠休息一下了,能夠給其餘線程一些工做機會啦。若是你以爲一個線程不那麼重要,或者優先級很是低,並且又懼怕它佔用太多的CPU資源,這時你能夠在適當時候調用yield()方法,給予其餘重要線程更多的工做機會。

volatile

    上一個例子有用到volatile關鍵字,volatile英文解釋爲"異變的,不穩定的"這也正是這個關鍵詞的語義。當你用volatile去申明一個變量時,就等於你告訴了虛擬機,這個變量極有可能會被某些程序或線程所修改。爲了確保修改後的變量可讓全部線程都可以「看到」這個改動,虛擬機就必須採用一些特殊的手段來保證這個變量的可見性等特色。

public class DiscoveryApplicaion {
    public static volatile int i = 0;
    static class Plustask implements Runnable{

        @Override
        public void run() {
            for (int k = 0; k < 1000; k++) {
                i++;
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread[] task = new Thread[10];
        for (int j = 0; j <task.length ; j++) {
            task[j] = new Thread(new Plustask());
            task[j].start();
            task[j].join();
        }
//        for (int j = 0; j < task.length; j++) {
//            task[j].join();
//        }
        System.out.println(i);
    }
}

    執行上述代碼,若是i++是原子性的,那麼最終的值應該是10000(10個線程各累加1000次)。但實際上放開註釋輸出老是會小於10000。

相關文章
相關標籤/搜索