1

So, the other day I made a fractal drawing program, and when it got too slow, I thought I would make it a bit faster by multithreading it. So I introduced a couple of threads to each draw a portion of the image. To know when I was done with the threads, I introduced two variables, AMOUNT_OF_THREADS and threadsDone. I created AMOUNT_OF_THREADS threads, that each drew their own partition and increased threadsDone when done. In the main method, I put in

while (threadsDone < AMOUNT_OF_THREADS)
    ;

It surprised me that that loop never finished looping. I knew it because each thread printed out "Thread done" and when the main method loop was done it should have printed out "Main done" but it didn't. I tried to print out threadsDone < AMOUNT_OF_THREADS and now the loop stopped. Weird.

I did some further testing and this problem seems only to occur if the thread takes >2ms to run or the loop takes >2ms each time it loops. Sometimes this bug occurs when the thread takes 2ms to run.

I suspect that the print function made the loop take >2ms to run and that made it work when I printed out the value, but I still wonder why this happens.

So my question is, why does this happen? Is it a bug in the compiler/JVM, or anything else? I asked my dad, and he replied after thinking for a few minutes that it has to be the while loop running too fast, but he wasn't sure.

Here's a little example of some code that has this problem:

public class WhileThread {

    public static boolean threadDone = false;

    public static void main(String[] args) {

        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                threadDone = true;

                System.out.println("Thread done.");
            }
        }).start();


        while (!threadDone) {

        }


        System.out.println("Main program done.");
    }

}

When you change the 0 in the Thread.sleep into a 10, this bug never happens.

If it matters, I use a Macbook Air with Java SE 8.

Siddhartha
  • 4,296
  • 6
  • 44
  • 65
Loovjo
  • 534
  • 8
  • 23
  • Is `threadsDone` being accessed from different threads? if so you should make it volatile. – Naveed Oct 15 '15 at 18:40
  • It's going to be very difficult for anyone to help you unless you post some code that demonstrates the problem you're having. – JJF Oct 15 '15 at 18:41
  • 1
    Just declare `threadsDone` as volatile ==> `public static volatile boolean threadDone = false;` – krokodilko Oct 15 '15 at 18:50
  • If it only is a threadDone boolean, you should take a look at thread.join(); And if you want to return something from a Thread, take a look at the Callable and Future interfaces. volatile fields can be tricky and depending on how you use them a performance hit. – Pinkie Swirl Oct 15 '15 at 18:50
  • @PinkieSwirl Yes, I know, but I wondered why this happens, not how to fix it – Loovjo Oct 15 '15 at 18:51
  • How much CPU time do you think that while loop is going to use? (Hint: 100%) – Solomon Slow Oct 15 '15 at 18:54
  • The thread context is not updated. Threads get their own context, with a copy of anything they need (the context). Variables that you use are only updated in that context. volatile triggers a context update if it is accessed by a thread (as does the synchronized block entry/exit). – Pinkie Swirl Oct 15 '15 at 18:54
  • 1
    changes made by 1 thread to a variable are not required to be made visible to other threads unless specific things happen that cause a happens-before barrier. and the optimizer is free to omit changes unprotected by that happens-before barrier when it reasons about the code, so the optimizer will likely discard tests on that variable. – Nathan Hughes Oct 15 '15 at 18:55

1 Answers1

1

So my question is, why does this happen? Is it a bug in the compiler/JVM, or anything else?

It's not a bug, it's a deliberate design decision. To make code like this work reliably, JVMs would have to assume that any thread could change any variable at any time unless they could prove that this couldn't happen. This would inhibit all kinds of extremely valuable optimizations and make Java code run much more slowly.

So instead, these optimizations were explicitly allowed and several methods (volatile, synchronized, and so on) were added to allow coders to show where variables could have their values changed by other threads.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278