簡體   English   中英

Java中的線程間通信

[英]Inter-thread communication in Java

為什么在以下Java代碼中:

public class WaitForOther {

private volatile boolean in = false;// need volatile for transitive closure
                                    // to occur



public void stoppingViaMonitorWait() throws Exception {

    final Object monitor = new Object();

    new Thread(new Runnable() {

        @Override
        public void run() {
            synchronized (monitor) {
                in = true;
                try {
                    monitor.wait(); // this releases monitor
                    //Thread.sleep(8000); //No-op
                    System.out.println("Resumed in "
                            + Thread.currentThread().getName());

                } catch (InterruptedException ignore) {/**/
                }
            }

        }
    }).start();

    System.out.println("Ready!");
    while (!in); // spin lock / busy waiting
    System.out.println("Set!");
    synchronized (monitor) {
        System.out.println("Go!");
        monitor.notifyAll();
    }

}

取消評論Thread.sleep(8000); //No-op Thread.sleep(8000); //No-op導致縮短輸出:

Ready! Set! Go!

否則在中斷的Thread-0中正確恢復:

Ready!
Set!
Go!
Resumed in Thread-0

這是JUnit測試,它調用上述行為,如評論中所要求的:

public class WaitForOtherTest {

    WaitForOther cut  = new WaitForOther();


    @Test
    public void testStoppingViaMonitorWait() throws Exception {
        cut.stoppingViaMonitorWait();
    }



}

謝謝!

我已經在JUnit中嘗試過你的測試了,我從你的經歷中得到了相反的結果:

  1. Thread.sleep被注釋掉時,測試運行正常並打印“Resumed in <>”
  2. Thread.sleep在代碼中(實際執行)時,JVM終止並且不會打印“Resumed in ...”。

原因是JUnit在測試完成后終止VM。 它做了一個System.exit(); 你很幸運得到了案例1中的完整輸出,因為它是在一個單獨的線程中打印的,而JUnit並沒有等待那個線程。

如果要在測試方法結束之前確保Thread已完成,則需要讓API等待線程,或者需要讓測試等待線程。

如果你的stoppingViaMonitorWait方法返回它創建的Thread,你可以等待測試。

@Test
public void testStoppingViaMonitorWait() throws Exception {
    Thread x = cut.stoppingViaMonitorWait();
    x.join();
}

另一種選擇是將一個線程池( ExecutorService一個實例)注入到您正在測試的類中,讓它在池上調度其線程(在任何情況下都更好),並且在您的測試方法中,您可以調用ExecutorService.awaitTermination

您的觀察與Java內存模型無關。 嘗試運行以下代碼:

new Thread() {
  @Override
  public void run() {
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Will not be printed when running JUnit");
  }
}.start();

如果你在main方法中運行它,那么線程 - 不是deamon線程 - 使進程保持活動狀態,直到睡眠周期結束,以便打印最后一行,然后進程結束。

由於JUnit正在測試可能損壞的代碼,因此需要假設從單元測試啟動的線程可能永遠不會結束。 因此,框架將不會等到所有啟動的線程都終止,而是在返回所有測試方法后顯式終止Java進程。 (假設您沒有為此次超時適用的測試設置超時。)這樣,JUnit測試套件不容易受到測試中的破壞代碼的影響。

但是,如果您的測試套件執行時間比啟動線程的運行時間長,您仍然可以看到打印的語句。 例如,通過添加另一個測試:

public class WaitForOtherTest {

  WaitForOther cut = new WaitForOther();

  @Test
  public void testStoppingViaMonitorWait1() throws Exception {
    cut.stoppingViaMonitorWait();
  }

  @Test
  public void testStoppingViaMonitorWait2() throws Exception {
    Thread.sleep(9000);
  }
}

你仍然可以看到印刷線。

但請注意,此打印結果相當不穩定,因為它依賴於特定的,非確定性的測試執行順序和非等待代碼的短運行時間。 (然而,它通常適用於運行這個示例,這對於演示目的來說是好的)。

此代碼濫用Java的低級線程通信結構,如waitnotify 目前尚不清楚您希望使用此代碼建立什么。

以下是我對程序行為的觀察(這些與您的行為不同。我在IDE和命令行中使用服務器和客戶端編譯器運行它):

隨着sleep() // 注釋掉 //:

Ready!
thread is daemon? : false
Set!
Go!
Resumed in Thread-0

使用sleep() 取消注釋

Ready!
thread is daemon? : false
Set!
Go!
<< 8 seconds pass >>
Resumed in Thread-0

因此,它符合您的設計: main線程啟動非守護程序線程(比如thread-0 )。 然后main線程和thread-0競爭鎖定( monitor ),但是thread-0總是勝出,因為你想要它。

如果thread-0抓起鎖,它設置了性寫in ,並立即放棄了鎖main線程通過通知它wait()荷蘭國際集團就可以了。 現在main線程看到易失性更新(可見性保證),中斷忙等待,抓取鎖定,打印“Go”並通知thread-0 ,根據其睡眠狀態得到通知。

由於忙碌的等待, main線程不會贏得鎖定的競爭。

我添加了一行來澄清你的線程是否是守護進程 ,因為你的線程是一個非守護進程線程,因此JVM沒有退出,它保持足夠長的時間讓線程從睡眠中喚醒並打印它的行。

有人說應該使用wait()在一個循環中等待一個條件變量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM