1. 程式人生 > >JAVA併發程式設計——執行緒協作通訊(二)

JAVA併發程式設計——執行緒協作通訊(二)

執行緒間的協作

在前面我們瞭解了很多關於同步(互斥鎖)的問題,下面來看一下執行緒之間的協作。這裡主要說一下Java執行緒中的join()、sleep()、yield()、wait()、notify()和notifyAll()方法。其中wait()、notify()和notifyAll()是執行緒間的協作的主要方法。

一、join()

join :讓一個執行緒等待另一個執行緒完成才繼續執行。如A執行緒執行緒執行體中呼叫B執行緒的join()方法,則A執行緒被阻塞,一直等到 B執行緒執行完為止,A才能得以繼續執行。類似於生活中排隊的時候,有人插隊,一直等到前面插隊的人的事情處理完才可以。

public class UseJoin {
	
    static class JumpQueue implements Runnable {
        private Thread thread;

        public JumpQueue(Thread thread) {
            this.thread = thread;
        }

        public void run() {
            try {
                thread.join();//呼叫傳入執行緒的join方法,必須等這個方法返回後,當前執行緒才能繼續執行
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName() + " terminate.");
        }
    }

    public static void main(String[] args) throws Exception {
        Thread previous = Thread.currentThread();//現在previous是主執行緒
        for (int i = 0; i < 10; i++) {
            // 每個執行緒擁有前一個執行緒的引用,需要等待前一個執行緒終止,才能從等待中返回
            Thread thread =
                    new Thread(new JumpQueue(previous), String.valueOf(i));
            System.out.println(previous.getName()+" jump a queue the thread:"
                    +thread.getName());
            thread.start();
            previous = thread;
        }

        SleepTools.second(2);//讓主執行緒休眠2秒
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }
}

可以看出,程式中定義了10個執行緒,將主執行緒作為引數傳遞給第一個執行緒,在run方法中呼叫了主執行緒的join方法,然後將第一個引數作為引數傳遞給第二個執行緒 ,在run方法中呼叫了第一個執行緒的join方法。。。所以,第一個執行緒要等到主執行緒執行完才能執行,第二個執行緒要等到第一個執行緒執行完才能執行。。。

main jump a queue the thread:0
0 jump a queue the thread:1
1 jump a queue the thread:2
2 jump a queue the thread:3
3 jump a queue the thread:4
4 jump a queue the thread:5
5 jump a queue the thread:6
6 jump a queue the thread:7
7 jump a queue the thread:8
8 jump a queue the thread:9
main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.
5 terminate.
6 terminate.
7 terminate.
8 terminate.
9 terminate.

二、sleep()

sleep:讓當前的正在執行的執行緒暫停指定的時間,並進入阻塞狀態。在其睡眠的時間段內,該執行緒由於不是處於就緒狀態,因此不會得到執行的機會。即使此時系統中沒有任何其他可執行的執行緒,出於sleep()中的執行緒也不會執行。因此sleep()方法常用來暫停執行緒執行。

前面有講到,當呼叫了新建的執行緒的start()方法後,執行緒進入到就緒狀態,可能會在接下來的某個時間獲取CPU時間片得以執行,如果希望這個新執行緒必然性的立即執行,直接呼叫原來執行緒的sleep(1)即可。

ublic class ThreadTest {

    public static void main(String[] args) {

        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);

        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                thread.start();
                try {
                    Thread.sleep(1);   // 使得thread必然能夠馬上得以執行
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

三、執行緒讓步:yield()

Java執行緒中的Thread.yield( )方法,即執行緒讓步。顧名思義,就是說當一個執行緒使用了這個方法之後,它就會把自己CPU執行的時間讓掉,讓自己或者其它的執行緒執行,注意是讓自己或者其他執行緒執行,並不是單純的讓給其他執行緒。

yield()的作用是讓步。它能讓當前執行緒由“執行狀態”進入到“就緒狀態”,從而讓其它具有相同優先順序的等待執行緒獲取執行權;但是,並不能保證在當前執行緒呼叫yield()之後,其它具有相同優先順序的執行緒就一定能獲得執行權;也有可能是當前執行緒又進入到“執行狀態”繼續執行!

舉個例子:
一幫朋友在排隊上公交車,輪到Yield的時候,他突然說:我不想先上去了,咱們大家來競賽上公交車。然後所有人就一塊衝向公交車,有可能是其他人先上車了,也有可能是Yield先上車了。 但是執行緒是有優先順序的,優先順序越高的人,就一定能第一個上車嗎?這是不一定的,優先順序高的人僅僅只是第一個上車的概率大了一點而已,最終第一個上車的,也有可能是優先順序最低的人。並且所謂的優先順序執行,是在大量執行次數中才能體現出來的。

四、wait()、notify()和notifyAll()

wait():導致當前執行緒等待並使其進入到等待阻塞狀態。直到其他執行緒呼叫該同步鎖物件的notify()或notifyAll()方法來喚醒此執行緒。

notify():喚醒在此同步鎖物件上等待的單個執行緒,如果有多個執行緒都在此同步鎖物件上等待,則會任意選擇其中某個執行緒進行喚醒操作,只有當前執行緒放棄對同步鎖物件的鎖定,才可能執行被喚醒的執行緒。

notifyAll():喚醒在此同步鎖物件上等待的所有執行緒,只有當前執行緒放棄對同步鎖物件的鎖定,才可能執行被喚醒的執行緒。

下面看一個例子:
在快遞郵寄過程中,公里數大於100的時候更新城市,當城市為ShangHai的時候顯示到站。在main方法中啟動了三個檢查里程數變化的執行緒,三個檢查地點變化的執行緒。
以下是示例程式碼:

public class Express {
    public final static String CITY = "ShangHai";
    private int km;/*快遞運輸里程數*/
    private String site;/*快遞到達地點*/

    public Express() {
    }

    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /* 變化公里數,然後通知處於wait狀態並需要處理公里數的執行緒進行業務處理*/
    public synchronized void changeKm(){
        this.km = 101;
        notifyAll();
    }

    /* 變化地點,然後通知處於wait狀態並需要處理地點的執行緒進行業務處理*/
    public  synchronized  void changeSite(){
        this.site = "BeiJing";
        notifyAll();
    }

    public synchronized void waitKm(){
        while(this.km<=100){//公里數小於100不做處理
            try {
                wait();
                System.out.println("Check Km thread["+Thread.currentThread().getId()
                		+"] is be notified");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the Km is "+this.km+",I will change db");
    }

    public synchronized void waitSite(){
        while(this.site.equals(CITY)){//快遞到達目的地
            try {
                wait();
                System.out.println("Check Site thread["+Thread.currentThread().getId()
                		+"] is be notified");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the site is "+this.site+",I will call user");
    }
}

public class TestWN {
    private static Express express = new Express(0,Express.CITY);

    /*檢查里程數變化的執行緒,不滿足條件,執行緒一直等待*/
    private static class CheckKm extends Thread{
        @Override
        public void run() {
        	express.waitKm();
        }
    }

    /*檢查地點變化的執行緒,不滿足條件,執行緒一直等待*/
    private static class CheckSite extends Thread{
        @Override
        public void run() {
        	express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<3;i++){
            new CheckSite().start();
        }
        for(int i=0;i<3;i++){
            new CheckKm().start();
        }

        Thread.sleep(1000);
        express.changeKm();//快遞里程數變化
        express.changeSite();//快遞地點變化
    }
}

Check Km thread[17] is be notified
the Km is 101,I will change db
Check Km thread[16] is be notified
the Km is 101,I will change db
Check Km thread[15] is be notified
the Km is 101,I will change db
Check Site thread[14] is be notified
the site is BeiJing,I will call user
Check Site thread[13] is be notified
the site is BeiJing,I will call user
Check Site thread[12] is be notified
the site is BeiJing,I will call user

五、wait()/notify()/nitifyAll()有關說明

1.、wait()方法執行後,當前執行緒立即進入到等待阻塞狀態,其後面的程式碼不會執行。

2、notify()/notifyAll()方法執行後,將喚醒此同步鎖物件上的執行緒物件,但是,此時還並沒有釋放同步鎖物件,也就是說,如果notify()/notifyAll()後面還有程式碼,還會繼續進行,知道當前執行緒執行完畢才會釋放同步鎖物件。

3、wait()/notify()/nitifyAll()完成執行緒間的通訊或協作都是基於不同物件鎖的,因此,如果是不同的同步物件鎖將失去意義。同時,同步物件鎖最好是與共享資源物件保持一一對應關係。

4、當wait執行緒喚醒後並執行時,是接著上次執行到的wait()方法程式碼後面繼續往下執行的。

5、wait()、notify()和notifyAll()方法是本地方法,並且為final方法,無法被重寫。

6、呼叫某個物件的wait()方法能讓當前執行緒阻塞,並且當前執行緒必須擁有此物件的鎖。

7、呼叫某個物件的notify()方法能夠喚醒一個正在等待這個物件的鎖的執行緒,如果有多個執行緒都在等待這個物件的鎖,則只能喚醒其中一個執行緒。

8、呼叫notifyAll()方法能夠喚醒所有正在等待這個物件的鎖的執行緒。

9、wait()、notify()和notifyAll()這三個不是Thread類宣告中的方法,而是Object類中宣告的方法,當然由於Thread類繼承了Object類,所以Thread也可以呼叫者三個方法。WHY?由於每個物件都擁有鎖,所以讓當前執行緒等待某個物件的鎖,當然應該通過這個物件來操作了。而不是用當前執行緒來操作,因為當前執行緒可能會等待多個執行緒的鎖,如果通過執行緒來操作,就非常複雜了。

10、呼叫某個物件的wait()方法,當前執行緒必須擁有這個物件的鎖,因此呼叫wait()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。

11、呼叫某個物件的wait()方法,相當於讓當前執行緒交出此物件的鎖,然後進入等待狀態,等待後續再次獲得此物件的鎖(Thread類中的sleep方法使當前執行緒暫停執行一段時間,從而讓其他執行緒有機會繼續執行,但它並不釋放物件鎖);

12、呼叫某個物件的notify()方法,當前執行緒也必須擁有這個物件的鎖,因此呼叫notify()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。

13、notify()和notifyAll()方法只是喚醒等待該物件的鎖的執行緒,並不決定哪個執行緒能夠獲取到鎖。

14、一個執行緒被喚醒不代表立即獲取了物件的鎖,只有等呼叫完notify()或者notifyAll()並退出synchronized塊,釋放物件鎖後,其餘執行緒才可獲得鎖執行。

六、呼叫yield() 、sleep()、wait()、notify()等方法對鎖有何影響

yield() :不會釋放鎖的。
sleep():不會釋放鎖的。
wait():呼叫前持有鎖,呼叫了wait方法以後,鎖會被釋放。
notify() :呼叫前持有鎖,包含了notify()的方法結束以後,鎖才會被釋放。