简体   繁体   中英

Variable 'runner' is not updated inside loop

Like this, I have two thread. The SleepRunner thread add some random numbers to a list then change flag to true and sleep. The main thread wait SleepRunner thread until the flag in SleepRunner object change from false to true then main thread will interrupte SleepRunner thread and the program will end.

But the question is, when the while loop is no body code in main thread, the variable 'runner' is not updated inside loop in other words The program is not over after SleepRunner thread change flag from false to true. So I tried to use debug tools in idea, but the program ended smoothly. And If I write some code, like System.out.println() or Thread.sleep(1) in while loop body at main thread, the program ended successfully too. it's too incredible? Does anyone know why this happens. Thanks.

public class Test1 {
        public static void main(String[] args) {
            SleepRunner runner = new SleepRunner();
            Thread thread = new Thread(runner);
            thread.start();
            while(!(runner.isFlag())){
                /*try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
            System.out.println("END");
            thread.interrupt();
        }
    }

public class SleepRunner implements Runnable {
        private boolean flag = false;
    
        public boolean isFlag() {
            return flag;
        }
    
        @Override
        public void run() {
            List<Integer> list = new ArrayList<>();
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep((long) (Math.random() * 200));
                }
                catch (InterruptedException e) {
                    System.out.println("Interrupted");
                }
                int num = (int) (Math.random() * 100);
                System.out.println(Thread.currentThread().getName() + " " + num);
                list.add(num);
            }
    
            flag = true;
    
            System.out.println("30 Seconds");
            try {
                Thread.sleep(30000);
            }
            catch (InterruptedException e) {
                System.out.println("Interrupted in 30 seconds");
            }
            System.out.println("sleep runner thread end");
        }
    }

You've violated the java memory model.

Here's how the JMM works*:

Each thread, whenever any field (from any object) is read or updated, flips a coin. On heads, it will make a copy and update/read from that. On tails, it won't. Your job is to ensure your code functions correctly regardless of how the coin lands, and you can't force the coinflip in a unit test. The coin need not be 'fair'. The coin's behaviour depends on the music playing in your music player, the whims of a toddler, and the phase of the moon. (In other words, any update/read may be done to a local cache copy, or not, up to the java implementation).

You may safely conclude that the only way to do it correctly, is to ensure the thread never flips that coin.

The way to accomplish that is to establish so-called 'comes before' relationships. Establishing them is done primarily by using synchronization primitives, or by calling methods that use synchronization primitives. For example, if I do this:

thread X:

synchronized(x) {
    x.foo();
    System.out.println(shared.y);
    shared.y = 10;
}

thread Y:

synchronized(x) {
    x.foo();
    System.out.println(shared.y);
    shared.y = 20;
}

then you've established a relationship: code block A comes before code block B, or vice versa, but you've at least established that they must run in order.

As a consequence, this will print either 0 10 or 0 20 , guaranteed. Without the synchronized block, it can legally print 0 0 as well. All 3 results would be an acceptable result (the java lang spec says it's okay, and any bugs filed that you think this makes no sense would be disregarded as 'working as intended').

volatile can also be used, but volatile is quite limited.

Generally, because this cannot be adequately tested , there are only 3 ways to do threading properly in java:

  1. 'in the large': Use a webserver or other app framework that takes care of the multithreading. You don't write the psv main() method, that framework does, and all you write are 'handlers'. None of your handlers touch any shared data at all. The handlers either don't share data, or share it via a bus designed to do it right, such as a DB in serializable transaction isolation mode, or rabbitmq or some other message bus.
  2. 'in the small': Use fork/join to parallellize a giant task. The handler for the task cannot, of course, use any shared data.
  3. read Concurrency in Practice (the book), prefer using the classes in the java.util.concurrent package, and in general be a guru about how this stuff works, because doing threading any other way is likely to result in you programming bugs which your tests probably won't catch, but will either blow up at production time, or will result in no actual multithreading (eg if you overzealously synchronize everything, you end up having all cores except one core just waiting around, and your code will actually run way slower than if it was just single threaded).

*) The full explanation is about a book's worth. I'm just giving you oversimplified highlights, as this is merely an SO answer.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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