9

In Java Concurrency in Practice one of the examples that I think surprises people (at least me) is something like this:

public class Foo {

  private int n;

  public Foo(int n) {
    this.n = n;
  }

  public void check() {
    if (n != n) throw new AssertionError("huh?");

  }
}

The surprise (to me at least) was the claim that this is not thread safe, and not only it's not safe, but also there is a chance that the check method will throw the assertion error.

The explanation is that without synchronization / marking n as volatile, there is no visibility guarantee between different threads, and that the value of n can change while a thread is reading it.

But I wonder how likely it is to happen in practice. Or better, if I could replicate it somehow.

So I was trying to write code that will trigger that assertion error, without luck.

Is there a straight forward way to write a test that will prove that this visibility issue is not just theoretical?

Or is it something that changed in the more recent JVMs?

EDIT: related question: Not thread safe Object publishing

Community
  • 1
  • 1
Eran Medan
  • 44,555
  • 61
  • 184
  • 276
  • Foo = new Foo(1) then check as you assign it to another Foo(2). – arynaq Jun 07 '13 at 21:57
  • 3
    Most concurrency bugs tend to be almost impossible to replicate with small, simple tests; they tend to appear at the worst possible time -- as part of a big, integrated system with lots of things going on. – Louis Wasserman Jun 07 '13 at 22:00
  • 1
    This won't happen on a combination of x86/hotspot today. It might happen in the future or in a different environment. – assylias Jun 07 '13 at 22:27
  • How can this happen? No thread should be able to access the object before the constructor returns. If the value of n was changed in a non-constructor method I could see the race condition but you would have to have the second thread get a reference to the object being constructed _during the execution of the constructor_, which AFAIK isn't possible unless the constructor itself "leaks" a reference. – Jim Garrison Jun 07 '13 at 22:52
  • @JimGarrison from the explanation my understanding is that the issue is visibility, and that it's not guaranteed. e.g. although the constructor has finished, the changes to n are not guaranteed to be visible to other threads immediately, so in theory, thread A creates an instance, and thread B is calling `check` before the changes to `n` are made visible to B. So as unlikely as it may sound, there is a chance where B is reading n for the left side of the comparison, before n's changes were made visible, then B reads the right side of the comparison operator and gets the updated value. – Eran Medan Jun 07 '13 at 23:00
  • Oops, almost exact duplicate: http://stackoverflow.com/questions/2624638/how-to-demonstrate-race-conditions-around-values-that-arent-published-properly?rq=1 – Eran Medan Jun 07 '13 at 23:02

3 Answers3

6

But I wonder how likely it is to happen in practice.

Highly unlikely esp as the JIT can turn n into a local variable and only read it one.

The problem with highly unlikely thread safety bugs is that one day, you might change something which shouldn't matter, like your choice of processor or JVM and suddenly your code breaks randomly.

Or better, if I could replicate it somehow.

There is not guarantee you can reproduce it either.

Is there a straight forward way to write a test that will prove that this visibility issue is not just theoretical?

In some cases, yes. but this one is a hard one to prove, partly because the JVM is not prevented from being more thread safe than the JLS says is the minimum.

For example, the HotSpot JVM often does what you might expect, not just the minimum in the docuemtation. e.g. System.gc() is only a hint according to the javadoc, but by default the HotSpot JVM will do it every time.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
3

This scenario is VERY unlikely, but still possible. The thread would have to be paused in the very moment the first n in the comparison is loaded, before the second one is and they are compared - and this takes so tiny a fraction of a second that you would have to be super lucky to hit it. But if you write this code in a super critical application that millions of users are going to use everyday, worldwide, it will happen sooner or later - it is only a matter of time.

There is no guarantee you can reproduce it - maybe it is even not possible on your machine. It depends on your platform, the VM, the Java compiler, etc...

You can convert the first n into a local variable, then pause the thread (Sleep), and have second thread change the n before you do the comparison. But I thing this scenario would defeat the purpose of demonstrating your case.

Jakub Zaverka
  • 8,816
  • 3
  • 32
  • 48
0

If a Foo is published unsafely, theoretically another thread could observe two different values when it reads n twice.

The following program could fail because of that reason.

public static Foo shared;

public static void main(String[] args)
{
    new Thread(){
        @Override
        public void run()
        {
            while(true)
            {
                Foo foo = shared;
                if(foo!=null)
                    foo.check();
            }
        }
    }.start();

    new Thread(){
        @Override
        public void run()
        {
            while(true)
            {
                shared = new Foo(1);  // unsafe publication
            }
        }
    }.start();
}

However it's almost impossible to observe that it fails; VM likely optimizes n!=n to false without actually reading n twice.

But we can show an equivalent program, i.e. a valid transformation of the previous program as far as Java Memory Model is concerned, and observe that it fails immediately

static public class Foo
{
    int n;

    public Foo()
    {
    }

    public void check()
    {
        int n1 = n;
        no_op();
        int n2 = n;
        if (n1 != n2)
            throw new AssertionError("huh?");
    }
}

// calling this method has no effect on memory semantics
static void no_op()
{
    if(Math.sin(1)>1) System.out.println("never");
}

public static Foo shared;

public static void main(String[] args)
{
    new Thread(){
        @Override
        public void run()
        {
            while(true)
            {
                Foo foo = shared;
                if(foo!=null)
                    foo.check();
            }
        }
    }.start();

    new Thread(){
        @Override
        public void run()
        {
            while(true)
            {
                // a valid transformation of `shared=new Foo(1)`
                Foo foo = new Foo();
                shared = foo;
                no_op();
                foo.n = 1;
            }
        }
    }.start();
}
ZhongYu
  • 19,446
  • 5
  • 33
  • 61