我试图理解java多线程,我在使用这个线程来完成它的运行方法时遇到了一些麻烦。
我有两个工作线程消耗阻塞队列中的消息。他们应该对它们传递的值做一些简单的事情并将结果附加到文件中。因为我不想为每个I / O操作打开文件阅读器,所以我想使用StringBuilder逐步构建报告,并在线程被踢出主页时将其全部写出来环。然而,循环之后的代码似乎永远不会被运行。
这是在线程中运行的代码:
@Override
public void run() {
StringBuilder builder = new StringBuilder();
while(true) {
if(!shouldRun) break;
try {
WorkMessage msg = (WorkMessage) ((BlockingQueue) inputQueue).take();
int payload = msg.payload;
LocalTime time = msg.timeCreated;
String report = "Adder got message at: " + time + ". Result: " + (payload + toAdd);
builder.append(report);
} catch (InterruptedException ie) {
System.out.println("ERROR IN ADDER: " + ie.getMessage());
}
}
writeToReportFile(builder.toString());
}
writeToReportFile
方法未被调用。当我在控制线程中将shouldRun
设置为false时,它似乎在while循环结束后跳过代码。我没有直接从外部线程将shouldRun
设置为false,我从外部调用内部kill()
方法,在内部将shouldRun
设置为false。
任何人都知道为什么会这样吗?我确定我在这里错过了一些简单的东西。
答案 0 :(得分:2)
由于take(),线程很可能被阻塞。而不是采取()为什么不用poll(time,timeunit)测试这段代码的方法将等待指定的时间然后返回所以在等待指定的时间后它会检查你的标志shouldRun并且会出来循环。
另外请将变量shouldRun声明为volatile。 原因:以下摘录来自实践中的书籍Java并发:
"服务器JVM执行比客户端JVM更多的优化,例如从循环中提取变量,而不是在循环中修改;"
因此,在使用服务器JVM的环境中,您可能最终处于无限循环中,因为在循环内未修改shouldRun变量。
答案 1 :(得分:1)
你应该使用Thread#interrupt来终止你的线程,你的手动标记方法会给你造成问题,因为对shouldRun标志的更新是不可见的,队列不能阻止阻塞。
如果您的控制线程在Worker对象上调用kill方法,则该线程将设置您的shouldRun标志。需要使用volatile使其可见,以便工作线程(执行run方法)可以看到它。
如果没有volatile(或使用AtomicBoolean或其他方法使这些更改在线程中可见),JVM可以自由地将变量的内容保存在另一个线程不可见的处理器本地缓存中,或者进行优化检查工作线程正在运行的代码。
使用队列之类的并发类的线程需要使用中断而不是依赖于手动标记,以便对它们包含的组件(如队列)的操作可以响应取消请求。否则,您的队列无法知道您的手动标记,因此它无法阻止响应对您的kill方法的调用。