[英]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.