开发者

How to demonstrate java multithreading visibility problems?

开发者 https://www.devze.com 2022-12-29 18:05 出处:网络
I开发者_如何学运维f variables in Java are accessed from multiple threads, one must ensure that they are safely published. This usually means using synchronizedor volatile.

I开发者_如何学运维f variables in Java are accessed from multiple threads, one must ensure that they are safely published. This usually means using synchronizedor volatile.

I have got the impression, that some of my colleagues do not take this issue seriously, since they "never heard of volatile before and their programs have worked for years".

So my question is:

Can someone provide an example Java program/snippet, that reliably shows data visibility problems.

I think running a program and seeing the unexpected NPE or stale variable value would help more, than just theoretic explanations, that cannot be demonstrated.

Thanks a lot for your help!

Update: Just to emphasis the point again. I have read Java Concurreny in Practice and know examples that theoretically have visibility issues. What I am looking for is a way to actually demonstrate them. I am not sure, that this is actually possible, but maybe there is a jvm configuration or something similar that allows it.


By modifying the example here by removing operations I have come up with an example that consistently fails in my environment (the thread never stops running).

// Java environment:
// java version "1.6.0_0"
// OpenJDK Runtime Environment (IcedTea6 1.6.1) (6b16-1.6.1-3ubuntu3)
// OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode)
public class Test2 extends Thread {
    boolean keepRunning = true;
    public static void main(String[] args) throws InterruptedException {
        Test2 t = new Test2();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }
    public void run() {
        while (keepRunning) 
        {}
    }
}

Note that this type of problems are quite dependent on the compiler/runtime/system. In particular the compiler can determine to add instructions to read the variable from memory even if it is not volatile --so the code would work--, the vm and jit can optimize away the reads from memory and use only registers, and even the processor can reorder instructions --that would not affect this case, but in other multithreaded cases it can affect the perceived state from other threads if more than one variable are modified.


The most common example brought up to emphasis the importance of using volatile is the while(keepRunning) example:

public class Test extends Thread {

    boolean keepRunning = true;

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }

    public void run() {
        while (keepRunning) 
            System.out.println(System.currentTimeMillis() + ": " + keepRunning);
    }
}

Since keepRunning may (validly) be kept in cache of the thread running the while-loop, this program may print "true" for keepRunning long after keepRunning is set to false.

Note however, that there is no reliable way of exposing race-conditions. (See my other answer.) This example may expose it under certain circumstances on certain combinations of hardware / operating system / jvm.


Can someone provide an example Java program/snippet, that reliably shows data visibility problems.

No, there is no reliably example that shows data visibility problems.

The reason is that any valid execution of a program with volatile is also a valid execution of the same program without volatile. (The opposite is obviously not true though!)


I have a snippet of code for you:

package test;

public class LoopTest {

 private boolean done = false;

 /**
  * @param args
  */
 public void start() {
  System.out.println(System.getProperty("java.vm.name"));
  System.out.println(System.getProperty("java.version"));
  for (int i = 0; i < 100; i++) {
   startNewThread();
  }

  try {
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  done = true;
  System.out.println("forcing end");
 }

 private void startNewThread() {
  new Thread(new Runnable() {

   public void run() {
    long i = 0;
    while(!done) {
     i++;
     if(i % 100L == 0) {
      System.out.println("still working " + i);
     }
    }
    System.out.println("ending " + i);
   }

  }).start();
 }

 public static void main(String[] args) {
  new LoopTest().start();
 }

}

This example run in JVM Server mode generated this output on my machine:

..
..
ending 14100
still working 14800
ending 14800
still working 26500
ending 26500
still working 18200
ending 18200
still working 9400
ending 9400
still working 1300
ending 1300
still working 59500
ending 59500
still working 1700
still working 75400
ending 75400
still working 33500
ending 33500
still working 36100
ending 36100
still working 121000
ending 121000
still working 3000
ending 3000
ending 1700
still working 5900
ending 5900
still working 7800
ending 7800
still working 7800
ending 7800
still working 6800
ending 6800
still working 5300
ending 5300
still working 9100
still working 10600
ending 10600
still working 9600
ending 9600
still working 10000
ending 10000
ending 9100
still working 1700
ending 1700
..
..

Look at the "ending #" statements: all of them have a number which is a multiple of 100 which is quite unlikely to happen, i think. My interpretation is that there is an visibility issue which causes the threads to still read done == false although it already has been updated to true. After the call to the synchronized System.out.println() method with the "still working #" statement, the threads read the updated value for done and exit.

Or does anyone see a mistake in my code / interpretation?


Make them read the book Java Concurrency in Practice by Java concurrency guru Brian Goetz. That book is a must-read for anybody who has to write any serious concurrent software in Java!

Ofcourse saying "I've never heard of volatile and my programs have worked for years" is a dumb argument from ignorance.


public class NoVisibility {

    private static boolean ready = false;
    private static int number;

    private static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ReaderThread().start();
        number = 42;
        Thread.sleep(20000);
        ready = true;
    }
}

Place the Thread.sleep() call for 20 secs what will happen is JIT will kick in during those 20 secs and it will optimize the check and cache the value or remove the condition altogether. And so the code will fail on visibility.

To stop that from happening you MUST use volatile.


An extension to @David's code (configuration Java6 64bit, Eclipse Juno SR2):

public class NoVisibility_Demonstration extends Thread {
    boolean keepRunning = true;
    public static void main(String[] args) throws InterruptedException {
        NoVisibility_Demonstration t = new NoVisibility_Demonstration();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println(System.currentTimeMillis() + ": keepRunning is false");
    }
    public void run() {
        int x = 10;
        while (keepRunning) 
        {
            //System.out.println("If you uncomment this line, the code will work without the visibility issue");
            x++;

        }
        System.out.println("x:"+x);
    }
}

Using this example, you can show both scenarios. When you uncomment the line in the while loop in run(), the visibility issue is resolved. The reason is that println() statements use synchronization. Detailed discussion HERE

0

精彩评论

暂无评论...
验证码 换一张
取 消