Java:为什么主线程没有进展?

时间:2016-03-20 15:20:04

标签: java multithreading

我遇到了一个多线程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

4 个答案:

答案 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然后你就让线程永远休眠。