[英]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中嘗試過你的測試了,我從你的經歷中得到了相反的結果:
Thread.sleep
被注釋掉時,測試運行正常並打印“Resumed in <>” 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的低級線程通信結構,如wait
和notify
。 目前尚不清楚您希望使用此代碼建立什么。
以下是我對程序行為的觀察(這些與您的行為不同。我在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.