我遇到了一个多线程Java程序的问题,并将其简化为一个非常简单的例子 - 我的困惑仍然不是很少!
下面显示的示例程序。那么这是做什么(或者打算做什么)?好吧,main()函数从一个简单的线程开始,基于一个静态的内部类Runnable。这个Runnable包含两个嵌套循环,它们对局部变量" z"进行简单的计算。对于总共10 ^ 12次迭代(10 ^ 6 * 10 ^ 6),之后它将打印出结果并退出。在产生这个工作线程之后,主线程进入它自己的循环,在那里它打印字符串" Z"到它之后的控制台(使用Thread.sleep())1秒钟,然后一遍又一遍地重复。
所以运行这个程序,我希望它打印出来" Z"每1秒计算线程正在完成其工作。
然而,事实上发生的是计算线程被启动,主线程显示第一个" Z"但没有任何反应。它似乎挂在Thread.sleep调用中,无论是无限的还是至少很多,比请求的1000毫秒长得多。
请注意,这是在具有多线程的快速四核机器上,因此同时运行线程应该没有问题。其他核心在Windows任务管理器中显示为空闲。此外,即使在单核系统上,我也希望操作系统定期抢占计算线程,以便允许主线程打印字符串。 此外,线程之间没有共享变量或锁定,因此它们不应该相互阻塞。
更奇怪的是,它似乎对行为至关重要,Runnable中有两个嵌套循环。只需一个循环,迭代次数相同,一切都按预期工作。
我已经使用Java 1.8.0_73在Windows 10 64位上对此进行了测试。
有人可以解释这种行为吗?
public class Calculate {
static class Calc implements Runnable
{
@Override
public void run() {
int z = 1;
for(int i = 0; i < 1000000; i++) {
for(int j = 0; j < 1000000; j++) {
z = 3*z + 1;
}
}
System.out.println("Result: " + z);
}
}
public static void main(String[] args) throws Exception
{
Thread t = new Thread(new Calc());
t.start();
while(true) {
System.out.println("Z");
Thread.sleep(1000);
}
}
}
更新1:有人建议这可能与Busy loop in other thread delays EDT processing重复。在我的情况下,许多症状是相同的,但很难说它们是否真的是同一个原因,因为另一个问题似乎没有被完全诊断出来。但这很有可能。
更新2:我已向Oracle提交了错误报告。如果被接受我将发布链接,我会找到它。
更新3:在Java 8和9中被接受为错误:https://bugs.openjdk.java.net/browse/JDK-8152267
答案 0 :(得分:0)
好像你的程序溢出我的机器我也只得到Z一次(Os x,i7,16G)。
编辑:你可以做些什么,以确保有机会主线程打印“z”每次你循环i时产生:
@Override
public void run() {
int z = 1;
for(int i = 0; i < 1000000; i++) {
Thread.yield();
for(int j = 0; j < 1000000; j++) {
z = 3*z + 1;
}
}
System.out.println("Result: " + z);
}
答案 1 :(得分:0)
正如我在描述中的“更新3”中所说,这是Java 8和9中的一个错误。所以这是这个问题的正确答案:程序没有任何问题,行为是由某些Java版本中的错误引起的,导致它不能正常运行。虽然在程序中插入暂停,产量等的建议可能与寻找实际解决方法的人有关,但“缺少这些解决方法”并不是问题的根本原因,并且不代表原始程序中的错误。
答案 2 :(得分:-1)
简短回答
这是CPU分配的问题。要解决此问题,请在Thread.currentThread().yield();
循环后添加行for (int j=0 ...)
。
答案很长
我使用以下修改过的程序来查找问题
public class Calculate {
public static final int[] LOOP_SIZES = {
1000, 5000, 10000, 50000, 65000, 66000, 100000, 500000, 1000000 };
static class Calc implements Runnable
{
private int loopSize;
public Calc(int loopsize) {
loopSize = loopsize;
}
@Override
public void run() {
int z = 1;
final long startMillis = System.currentTimeMillis();
for(int i = 0; i < loopSize; i++) {
for(int j = 0; j < loopSize; j++) {
z = 3*z + 1;
}
Thread.currentThread().yield(); // comment this line to reproduce problem
}
final long endMillis = System.currentTimeMillis();
final double duration = ((double)( endMillis - startMillis )) / 1000.0;
System.out.println("Result for " + loopSize +
": " + z +
" @ " + duration + " sec");
}
}
public static void main(String[] args) throws Exception
{
Thread tt[] = new Thread[LOOP_SIZES.length];
for (int i = 0; i < LOOP_SIZES.length; i++) {
final int ls = LOOP_SIZES[i];
tt[i] = new Thread(new Calc(ls));
tt[i].start();
}
int nbNonTerminated = 1;
while(nbNonTerminated > 0) {
nbNonTerminated = 0;
for (int j=0; j < tt.length; j++) {
printThreadState( tt[j], "Thread " + j );
if (!tt[j].getState().equals(java.lang.Thread.State.TERMINATED))
nbNonTerminated ++;
}
printThreadState( Thread.currentThread(), "Main thread");
System.out.println("Z @ " + System.currentTimeMillis());
Thread.sleep(1000);
}
}
public static void printThreadState( Thread t, String title ){
System.out.println( title + ": " + t.getState() );
}
}
如果运行此程序,主线程按预期工作(它每秒打印一条消息)。如果用yield
注释该行,重新编译并运行它,主循环将打印一些消息,然后是块。似乎JVM几乎将所有CPU分配给其他线程,导致主线程无响应。
线程数似乎没有改变问题。当“循环大小”变得大于65000时,问题就开始出现。另外,请注意我完成时没有加入线程。
注意:在Mac OSX,2.7.5,2.5 GHz Core i5上测试 我还在Raspberry Pi 2,Raspbian linux 4.1.3(一台效率低得多的机器)上进行了测试:问题没有显示出来。
答案 3 :(得分:-5)
在run方法中你没有循环,只是简单的z计算。 因此,当你调用t.start(); run方法被调用一次,计算z然后你就让线程永远休眠。