[英]Thread.sleep blocks other Thread
我有一個Output類,它僅打印將要打印的所有內容。
public class Output {
private static List<String> textList = new ArrayList<>();
private static Output output = null;
private Output() {
Runnable task = () -> {
int lastIndex = 0;
while (true) {
while (lastIndex < textList.size()) {
System.out.println(lastIndex + " - " + textList.size() + ": " + textList.get(lastIndex));
outputText(textList.get(lastIndex));
lastIndex ++;
}
}
};
new Thread(task).start();
}
private static void outputText(String text) {
synchronized (System.out) {
System.out.println(text);
}
}
public static void say(String text) {
if (output == null) {
output = new Output();
}
textList.add(text);
}
}
當我添加打印內容時,一切正常:
for (int i = 0; i < 10; i++) {
Output.say("" + i);
}
但是,當我將Thread.sleep
添加到循環中時,它將在第一個輸出上停止:
for (int i = 0; i < 10; i++) {
Output.say("" + i);
Thread.sleep(100);
}
我該如何預防? 我的意思是,我將僅在主線程而不是單獨的線程處於休眠狀態。
如果您沒有正確同步線程,則無法保證線程會看到其他線程所做的更新。 他們要么完全錯過更新,要么只看到其中的一部分,從而造成完全不一致的結果。 有時他們甚至看起來似乎在做正確的事情。 如果沒有適當的同步(就任何指定為線程安全的有效構造而言),這是完全不可預測的。
有時,像您的示例一樣,看到特定行為的幾率更高。 在大多數運行中,沒有sleep
的循環將在另一個線程甚至開始工作之前完成,而插入sleep
會增加第二個線程看到值后丟失更新的機會。 一旦第二個線程看到了textList.size()
的值,它可能會永遠重復使用該值,將lastIndex < textList.size()
評估為false
並執行while(true) { }
的等效操作。
有趣的是,為確保線程安全而插入結構的唯一位置是僅由單個線程調用的outputText
方法(無論如何,在大多數環境中,打印到System.out
都是內部同步的)。
此外,由於所有字段和方法都是static
,因此尚不清楚為什么要創建與此處不相關的Output
類型的對象。
您的代碼可以更正並簡化為
public static void main(String[] args) throws InterruptedException {
List<String> textList = new ArrayList<>();
new Thread( () -> {
int index=0;
while(true) synchronized(textList) {
for(; index<textList.size(); index++)
System.out.println(textList.get(index));
}
}).start();
for (int i = 0; i < 10; i++) {
synchronized(textList) {
textList.add(""+i);
}
Thread.sleep(100);
}
}
盡管它仍然包含您的原始代碼的問題,這些代碼永遠不會由於無限的第二個線程而終止,並且還會通過輪詢循環來燒毀CPU。 您應該讓第二個線程等待新項並添加終止條件:
public static void main(String[] args) throws InterruptedException {
List<String> textList = new ArrayList<>();
new Thread( () -> {
synchronized(textList) {
for(int index=0; ; index++) {
while(index>=textList.size()) try {
textList.wait();
} catch(InterruptedException ex) { return; }
final String item = textList.get(index);
if(item==null) break;
System.out.println(item);
}
}
}).start();
for (int i = 0; i < 10; i++) {
synchronized(textList) {
textList.add(""+i);
textList.notify();
}
Thread.sleep(100);
}
synchronized(textList) {
textList.add(null);
textList.notify();
}
}
這仍然只是一個學術示例,您不應該在現實生活中使用它。 Java API提供了用於線程安全的數據交換的類,從而減輕了您自己實現此類事情的負擔。
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
String endMarker = "END-OF-QUEUE"; // the queue does not allow null
new Thread( () -> {
for(;;) try {
String item = queue.take();
if(item == endMarker) break;// don't use == for ordinary strings
System.out.println(item);
} catch(InterruptedException ex) { return; }
}).start();
for (int i = 0; i < 10; i++) {
queue.put(""+i);
Thread.sleep(100);
}
queue.put(endMarker);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.