程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java多線程根底——線程間通訊

Java多線程根底——線程間通訊

編輯:關於JAVA

Java多線程根底——線程間通訊。本站提示廣大學習愛好者:(Java多線程根底——線程間通訊)文章只能為提供參考,不一定能成為您想要的結果。以下是Java多線程根底——線程間通訊正文


在運用多線程的時分,常常需求多個線程停止協作來完成一件事情。在後面兩章剖析了Java多線程的根本運用以及應用synchronized來完成多個線程同步伐用辦法或許執行代碼塊。但下面兩章的內容觸及到的例子以及運用的多線程代碼都是單獨運轉,兩個順序除了競爭同一個對象以外,沒有任何聯絡。

這次內容將解說當多個線程需求協作來完成一件事情的時分,如何去等候其他線程執行,又如何當線程執行完去告訴其他線程完畢等候。
本次次要引見如下內容:

  • 等候/告訴機制
  • join辦法的運用

一切的代碼均在char03線程間通訊

等候/告訴機制

Java中對多線程類提供了兩個辦法來完成等候/告訴機制,等候的辦法是-wait(),告訴的辦法是notify()。先說一下什麼是等候/告訴機制,所謂等候/告訴機制,就是線程A在執行的時分,需求一個其他線程來提供的後果,但是其他線程還沒有通知他這個後果是什麼,於是線程A開端等候,當其他線程計算出後果之後就將後果告訴給線程A,A線程喚醒,持續執行。這個進程就是等候/告訴機制。
等候/告訴機制實踐上多個線程之間的一種互動,而為了保證這個互動僅限於希冀的那些線程,因而需求多個線程擁有一個一致的對象監視器,也就是都要在synchronized(x)同步代碼塊中執行x.wait以及x.notify辦法。

假如細心察看,會發現wait辦法和notify辦法是Object類自帶的辦法。這個緣由是由於任何一個對象都能成為監視器,而wait和notify只要對同一個監視器才干起到預期的作用。也就是說任何一個監視器都能用wait以及notify辦法,任何對象都有的辦法,自然就需求放到Object中

wait辦法與notify辦法的解說

wait辦法會使執行該wait辦法的線程中止,直到等到了notify的告訴。細說一下,執行了wait辦法的那個線程會由於wait辦法而進入等候形態,該線程也會進入阻塞隊列中。而執行了notify那個線程在執行完同步代碼之後會告訴在阻塞隊列中的線程,使其進入就緒形態。被重新喚醒的線程會試圖重新取得臨界區的控制權,也就是對象鎖,然後持續執行臨界區也就是同步語句塊中wait之後的代碼。
下面這個描繪,可以看出一些細節。

  1. wait辦法進入了阻塞隊列,而上文講過執行notify操作的線程與執行wait的線程是擁有同一個對象監視器,也就說wait辦法執行之後,立即釋放掉鎖,這樣,另一個線程才干執行同步代碼塊,才干執行notify。
  2. notify線程會在執行完同步代碼之後告訴在阻塞隊列中的線程,也就是說notify的那個線程並不是立刻釋放鎖,而是在同步辦法執行完,釋放鎖當前,wait辦法的那個線程才會持續執行。
  3. 被重新喚醒的線程會試圖重新取得鎖,也就說,在notify辦法的線程釋放掉鎖當前,其告訴的線程是不確定的,看詳細是哪一個阻塞隊列中的線程獲取到對象鎖。

上面看一個例子:

public class Service {
    public void testMethod(Object lock){
        try{
            synchronized (lock){
                System.out.println("begin wait()");
                lock.wait();
                System.out.println(" end wait()");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public void synNotifyMethod(Object lock){
        try{
            synchronized (lock){
                System.out.println("begin notify() ThreadName=" + Thread.currentThread().getName() +
                        " time=" +System.currentTimeMillis());
                lock.notify();
                Thread.sleep(1000 * 1);
                System.out.println("end notify() ThreadName=" + Thread.currentThread().getName() +
                        " time=" + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

該Service中有兩個辦法,一個是testMethod辦法,包括了wait辦法,另一個是synNotifyMethod辦法了notify辦法,我們首先看一下,wait辦法會釋放鎖的測試。

public class ServiceThread extends Thread{
    private Object lock;

    public ServiceThread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        super.run();
        Service service = new Service();
        service.testMethod(lock);
    }
}

測試辦法如下:

public void testRun() throws Exception {
    Object lock = new Object();
    new ServiceThread(lock).start();
    new ServiceThread(lock).start();
    Thread.sleep(1000 * 4);
}

後果如下:

begin wait()
begin wait()

很分明後果是執行了2次同步代碼塊,其執行的緣由,就是由於第一個wait之後,釋放掉了對象鎖,所以第二個線程才會執行同步代碼塊。

還是應用下面的代碼,如今我們看一下,notify辦法告訴等候的線程, 但是不會立刻釋放鎖的例子。

public class NotifyServiceThread extends Thread{
    private Object lock;
    public NotifyServiceThread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        super.run();
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}

測試的例子如下:

public class NotifyServiceThreadTest extends TestCase {
    public void testRun() throws Exception {
        Object lock = new Object();
        ServiceThread a = new ServiceThread(lock);
        a.start();
        Thread.sleep(1000);
        new NotifyServiceThread(lock).start();
        new NotifyServiceThread(lock).start();

        Thread.sleep(1000 * 10);
    }

}

其後果如下:

begin wait()
begin notify() ThreadName=Thread-1 time=1484302436105
end notify() ThreadName=Thread-1 time=1484302437108
end wait()
begin notify() ThreadName=Thread-2 time=1484302437108
end notify() ThreadName=Thread-2 time=1484302438110

測試辦法,首先調用上wait的例子,讓ServiceThread線程進入等候形態,然後執行2個含有notify操作的線程,可以看出,第一個notify執行完,wait線程並沒有立刻開端運轉,而是Thread-1持續執行後續的notify辦法,直到同步語句塊完畢,然後wait線程立刻失掉鎖,並持續運轉。之後Thread-2開端運轉,直到完畢,由於曾經沒有等候的線程,所以不會有後續的等候的線程運轉。

這裡,可以看出一個細節,競爭鎖的線程有3個,一個包括wait線程,兩個包括notify線程。第一個notify執行完畢,取得鎖一定是阻塞的線程,而不是另一個notify的線程。

下面的順序展示了等候/告訴機制是如何經過wait和notify完成。在這裡,我們可以看出wait辦法使線程進入等候,和Thread.sleep是很類似的。但是兩者卻一模一樣,區別如下:

  • wait使線程進入等候,是可以被告訴喚醒的,但是sleep只能自己到時間喚醒。
  • wait辦法是對象鎖調用的成員辦法,而sleep卻是Thread類的靜態辦法
  • wait辦法呈現在同步辦法或許同步代碼塊中,但是sleep辦法可以呈現在非同步代碼中。

wait和notify還提供了幾個其他API,如wait(long timeout)該辦法可以提供一個喚醒的時間,假如在時間內,沒有其他線程喚醒該等候線程,則到設定的時間,會自動完畢等候。
由於notify僅僅能喚醒一個線程,所以Java提供了一個notifyAll()的辦法來喚醒一切的線程,讓一切的線程來競爭。我們看一下只喚醒一個線程和喚醒一切線程的不同。

public class CommonWait {

    private Object object;
    public CommonWait(Object object){
        this.object = object;
    }

    public void doSomething() throws Exception{
        synchronized (object){
            System.out.println("begin wait  " + Thread.currentThread().getName());
            object.wait();
            System.out.println("end wait " + Thread.currentThread().getName());
        }
    }
}
public class CommonNotify {

    private Object object;
    public CommonNotify(Object object){
        this.object = object;
    }

    public void doNotify(){
        synchronized (object){
            System.out.println("預備告訴");
            object.notify();
            System.out.println("告訴完畢");
        }
    }
}

測試告訴一個等候線程

 public void testRun() throws Exception{
        Object lock = new Object();
        new Thread(()->{
            try {
                new CommonWait(lock).doSomething();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try {
                new CommonWait(lock).doSomething();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();

        Thread.sleep(1000);

        new Thread(()->{
            new CommonNotify(lock).doNotify();
        }).start();

        Thread.sleep(1000 * 3);

    }
    

後果如下:

begin wait  Thread-0
begin wait  Thread-1
預備告訴
告訴完畢
end wait Thread-0

後果看來,只要一個線程完畢了等候,持續往上面執行。另一個線程直到完畢也沒有執行。
如今看一下notifyAll的效果,把CommonNotify這個類中的object.notify();改成object.notifyAll()
其他的不變,看看後果:

begin wait  Thread-0
begin wait  Thread-1
預備告訴
告訴完畢
end wait Thread-1
end wait Thread-0

很分明,兩個等候線程都執行了,而且這次Thread-1的線程先執行,可見告訴喚醒是隨機的。
這裡詳細說一下,這個後果。wait使線程進入了阻塞形態,阻塞形態可以細分為3種:

  • 等候阻塞:運轉的線程執行wait辦法,JVM會把該線程放入等候隊列中。
  • 同步阻塞:運轉的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池當中。
  • 其他阻塞: 運轉的線程執行了Thread.sleep或許join辦法,或許收回I/O懇求時,JVM會把該線程置為阻塞形態。當sleep()形態超時、join()等候線程終止,或許超時、或許I/O處置終了時,線程重新轉入可運轉形態。

可運轉形態就是線程執行start時,就是可運轉形態,一旦CPU切換到這個線程就開端執行外面的run辦法就進入了運轉形態。
下面會呈現這個後果,就是由於notify僅僅讓一個線程進入了可運轉形態,而另一個線程則還在阻塞中。而notifyAll則使一切的線程都從等候隊列中出來,而由於同步代碼的關系,取得鎖的線程進入可運轉態,沒有失掉鎖的則進入鎖池,也是阻塞形態,但是會由於鎖的釋放而重新進入可運轉態。所以notifyAll會讓一切wait的線程都會持續執行。

join辦法的運用

wait辦法使線程進入阻塞,並且由於告訴而喚醒執行,sleep辦法異樣使線程進入阻塞,並且因而超時而完畢阻塞。以上兩者都是由於特定的條件而完畢阻塞,如今主線程需求知道子線程的後果再持續執行,這個時分要怎樣做,用告訴/等候不是很容易完成這個操作,sleep則完全不知道要等候的時間。因而Java提供了一個join()辦法,join()辦法是Thread對象的辦法,他的功用是使所屬的線程對象x正常執行run辦法的內容,而使以後線程z停止有限期的阻塞,等候線程x銷毀後在持續執行線程z前面的代碼。這說起來有點繞口,其實看例子就很復雜。

public class JoinThread extends Thread{
    @Override
    public void run() {
        super.run();
        try{
            int secondValue = (int)(Math.random() * 10000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

其測試的辦法如下:

    public void testRun() throws Exception {
        JoinThread joinThread = new JoinThread();
        joinThread.start();
        joinThread.join();
        System.out.println("我想當Join對象執行終了後我再執行,我做到了");

    }

後果如下:

3519
我想當Join對象執行終了後我再執行,我做到了

看上去join辦法很神奇,可以完成線程在執行下面的次第。但是實踐上join辦法外部是經過wait完成的。

 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的原理很復雜,後面那些if條件不論,次要看while循環外面的,while循環就是不時去判別this.isAlive的後果,用下面的例子,這個this就是joinThread。然後關鍵的代碼就是wait(delay);一個定時的wait。這個wait的對象也是this,就是joinThread。下面我們曾經講了wait一定要在同步辦法或許同步代碼塊中,源碼中join辦法的修飾符就是一個synchronized,標明這是一個同步的辦法。
不要看調用wait是joinThread,是一個線程。但是真正由於wait進入阻塞形態的,是持有對象監視器的線程,這裡的對象監視器是joinThread,持有他的是main線程,由於在main線程中執行了join這個同步辦法。

所以main線程不時的wait,直到調用join辦法那個線程對象銷毀,才持續向下執行。

但是源碼中只要wait的辦法,沒有notify的辦法。由於notify這個操作是JVM經過檢測線程對象銷毀而調用的native辦法,是C++完成的,在源碼中是找不到對應這個wait辦法而存在的notify辦法的。

總結

這裡引見了線程間通訊的一種罕見的方式——等候/告訴機制。此外,還引見了一種指定線程執行順序的辦法——join辦法,並且解說了其外部的完成。
全部的代碼都在char03線程間通訊


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved