简体   繁体   English

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

[英]Java: Why is the main-thread not progressing?

I ran into some problems with a multi-threaded Java-program and distilled it down to a very simple example - still my puzzlement isn't less! 我在使用多线程Java程序时遇到了一些问题,并将其提炼为一个非常简单的示例-我的困惑仍然不少!

The example program it shown below. 示例程序如下所示。 So what does this do (or was intended to do)?. 那么这是做什么的(或打算要做的)? Well, the main() function starts off a simple thread, based on a static, inner class Runnable. 好吧,main()函数基于一个静态的内部类Runnable从一个简单的线程开始。 This Runnable contains two nested loops, that does a simple calculation on a local variable "z" for a total of 10^12 iterations (10^6 * 10^6), after which it will print out the result and exit. 此Runnable包含两个嵌套循环,该循环对局部变量“ z”进行简单计算,总共进行10 ^ 12次迭代(10 ^ 6 * 10 ^ 6),此后将打印出结果并退出。 After spawning this worker thread, the main thread goes into a loop of its own where it prints the string "Z" to the console after which it sleeps (using Thread.sleep()) for 1 second, then repeats this over and over. 产生该工作线程之后,主线程进入其自己的循环,在该循环中,它将字符串“ Z”打印到控制台,然后进入睡眠状态(使用Thread.sleep())1秒钟,然后重复一次。

So running this program, I would expect it to print out "Z" every 1 second while the calculating thread was doing its job. 因此,运行该程序,我希望它在计算线程执行其工作时每1秒打印一次“ Z”。

However, in fact what happens is that the calculating thread is started, and the main thread displays the first "Z" but then nothing happens. 但是,实际上发生的是启动了计算线程,并且主线程显示了第一个“ Z”,但是随后什么也没有发生。 It appears to hang in the Thread.sleep call, either infinitely or at least much, much longer than the requested 1000 ms. 它似乎无限期地挂在Thread.sleep调用中,或者至少比请求的1000 ms更长。

Note that this is on a fast, quad-core machine with multi-threading, so there should be no trouble in running the threads simultaneously. 请注意,这是在具有多线程的快速四核计算机上,因此同时运行线程应该没有问题。 The other cores appear as idle in Windows task manager. 其他核心在Windows任务管理器中显示为空闲。 Also, even on a single-core system I would expect the operating system to periodically preempt the calculating thread, in order to allow the main-thread to print the strings. 另外,即使在单核系统上,我也希望操作系统会定期抢占计算线程,以便允许主线程打印字符串。 Further, there's no shared variables or lock between the threads, so they shouldn't be able to block each other. 此外,线程之间没有共享变量或锁,因此它们不应相互阻塞。

Even more strangely, it appears to be critical for the behavior, that there are two nested loops in the Runnable. 更奇怪的是,对于该行为而言,至关重要的是,Runnable中有两个嵌套循环。 With just one loop, with the same number of iterations, everything works as expected. 只需一个循环,迭代次数相同,一切都会按预期进行。

I've tested this on Windows 10 64-bit with Java 1.8.0_73. 我已经在Windows 10 64位Java 1.8.0_73上对此进行了测试。

Can someone explain this behavior? 有人可以解释这种行为吗?

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);
        }
    }
}

Update 1: It has been suggested that this might be a duplicate of Busy loop in other thread delays EDT processing . 更新1:建议在其他线程延迟EDT处理中 ,这可能是Busy循环的副本。 Many symptoms in my case are the same, but it is hard to tell if they truly are the same cause, because the other problem doesn't appear to have been fully diagnosed. 在我的情况下,许多症状是相同的,但是很难确定它们是否确实是相同的原因,因为似乎还没有完全诊断出另一个问题。 But it is a likely possibility. 但这是可能的。

Update 2: I have filed a bug report with Oracle. 更新2:我已经向Oracle提交了错误报告。 I will post link if it is accepted and I find it. 我会发布链接,如果它被接受并且找到了。

Update 3: Accepted as bug in Java 8 and 9: https://bugs.openjdk.java.net/browse/JDK-8152267 更新3:被Java 8和9接受为错误: https : //bugs.openjdk.java.net/browse/JDK-8152267

Seems like your program overflow my machine I also only get Z once (Os x, i7, 16G). 好像您的程序溢出了我的机器,我也只得到Z一次(Os x,i7、16G)。

Edit : What you could do to be sure to give a chance to the main Thread to print "z" is yield every time you loop over i : 编辑:您可以做的确保给主线程打印“ 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);
}

As I have put in in "update 3" in the description, this turned out to be a bug in Java 8 and 9. So this is the correct answer to this question: There is nothing wrong with the program, the behavior is caused by bugs in certain versions of Java, causing it to not behave as it should. 正如我在描述中输入的“更新3”中所述,这原来是Java 8和9中的错误。因此,这是此问题的正确答案:程序没有问题,其行为是由某些Java版本中的错误,导致其行为不正常。 While suggestions of inserting pauses, yields etc. in the program might be relevant for someone looking for a practical workaround, the "lack of these workarounds" is not the root cause of the problem and do not represent an error in the original program. 尽管在程序中插入暂停,收益等的建议可能与某些寻求实际解决方法的人有关,但“这些解决方法的不足”不是问题的根本原因,也不表示原始程序中有错误。

Short answer 简短答案

It is a problem of CPU allocation. 这是CPU分配的问题。 To solve it, add the line Thread.currentThread().yield(); 要解决该问题,请添加Thread.currentThread().yield(); after the for (int j=0 ...) loop. for (int j=0 ...)循环之后。

Long answer 长答案

I used the following modified program to find the problem 我使用以下修改程序来查找问题

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() );
    }
}

If you run this program, the main thread works as intended (it prints a message every second). 如果运行此程序,则主线程将按预期工作(每秒打印一条消息)。 If you comment the line with the yield , recompile and run it, the main loop prints a few messages, then blocks. 如果用yield注释该行,然后重新编译并运行它,则主循环将打印一些消息,然后进行阻塞。 It seems like the JVM allocates almost all CPU to the other threads, rendering the main thread unresponsive. 看来JVM几乎将所有CPU分配给其他线程,从而使主线程无响应。

The number of threads did not seem to change the problem. 线程数似乎并未改变问题。 The problem started to appear when the "loop size" became larger than about 65000. Also, note I did not join threads when they finish. 当“循环大小”变得大于大约65000时,问题开始出现。另外,请注意,完成线程后,我没有加入线程。

note: tested on Mac OSX, 2.7.5, 2.5 GHz Core i5. 注意:在Mac OSX,2.7.5、2.5 GHz Core i5上进行了测试。
I also tested it on a Raspberry Pi 2, Raspbian linux 4.1.3 (a much less powerful machine): the problem does not show. 我还在Raspberry Pi 2,Raspbian linux 4.1.3(功能不那么强大的机器)上进行了测试:问题未显示。

Inside the run method you have no loop, just a simple calculation of z. 在run方法中,您没有循环,只需简单地计算z。 Thus when you call t.start(); 因此,当您调用t.start(); the run method is called once, calculates the z and then you just put the thread to sleep forever. run方法被调用一次,计算z值,然后将线程永久休眠。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM