0

there have been already similar questions, but it doesn't answer the following problem. It's well known that values of fields are not necessarily immediately synchronized between threads. But is this also the case with local variables? Can the IllegalStateException be thrown?

public static void main(String[] args) {
    final Thread mainThread = Thread.currentThread();
    final Integer[] shared = new Integer[1];

    new Thread(new Runnable() {
        @Override
        public void run() {
            shared[0] = 1;
            mainThread.interrupt();
        }
    }).start();

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        if (shared[0] == null) throw new IllegalStateException("Is this possible?");
    }
}
Jörg Vollmer
  • 198
  • 1
  • 7
  • Local variables that are visible to more than one thread *are not thread safe.* They have to be accessed through the regular mechanisms (synchronized, volatile, immutable, etc.). – markspace Dec 10 '15 at 21:51
  • 2
    Don't confuse variables and the values they store. – Sotirios Delimanolis Dec 10 '15 at 21:56
  • @jameslarge Yes, I realize the difference. However anybody who needs to ask this question might be a newbie and I'd rather just say "the local variable". I think Java makes a synthetic copy of the variable in the inner class, and then it becomes more obvious that there's really two references here. But that's getting rather complicated. Short answer: nope, this example is not thread safe. – markspace Dec 10 '15 at 22:01

6 Answers6

6

Indeed, the value of shared will be the same for all threads. But the value of shared[0] also involves reading an array element, and that array element, like a field, may be subject to a data race.

Are you sure about shared being safe?

Yes, the Java Language Specification writes:

Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.

At the JVM level, each thread has its own local variables. If an anonymous class accesses a local variable of an enclosing method, the compiler rewrites this code to pass the value of the variable as a constructor parameter to the inner class, which will store it in a final field (this rewriting is why the compiler requires such a variable to be effectively final and definitely assigned), and replaces all accesses to this variable by an access to the final field. Due to the special guarantees the Java Memory Model gives for final fields, this access is safe even if it the object reference is published through a data race, provided that such publication only occurs after the object has completed construction.

meriton
  • 68,356
  • 14
  • 108
  • 175
  • Are you sure about `shared` being safe? I'd like to find that in the Java Language Specification. It would be handy to be assured that in this case `shared`, the reference to the array, is handled safely by the Java runtime. – markspace Dec 10 '15 at 21:56
  • Right, final fields are thread safe (or at least safely published). But where does it say that Java stores that variable in a final field in the Spec? That's what I'm after. – markspace Dec 10 '15 at 22:05
  • It does not mandate that particular implementation technique, but it does require that local variables are never shared between threads, and unaffected by threading issues. – meriton Dec 10 '15 at 22:38
3

Local variables are perfectly thread safe, because there is no way to share them with another thread in the first place.

Your example code is a wholly different beast, because you are actually asking about the value of a shared array referred to by a local variable. Thats two different things. The variable is perfectly safe (cannot change anyway, since its final), the contents of the array it refers to is not synchronized in any way, so its also not safe.

Edit: To elaborate a bit about your variable named "shared" When you declare a local variable as final, java allows you to refer to that variable in the scope of an anonymous class defined within the visibility scope of said variable (Put simpler: from within the block where the variable was defined).

What looks like one variable, are actually two variables. The one you declared exists in the main thread. The moment the anonymous "new Runnable()" is created, a copy of the variable contents is made (it actually becomes a hidden final field in the anonymous class). So when you refer to "shared" within the run()-method you do not access the local variable "shared" in the main thread.

You can verify this by looking at the class files your example creates (there are two, one for the class, and one for the anonymous class) and use javap -v for both to have a look at the byte code generated.

Durandal
  • 19,919
  • 4
  • 36
  • 70
0

Local variables that are visible to more than one thread are not thread safe. They have to be accessed through the regular mechanisms (synchronized, volatile, immutable, etc.).

Normally, you create a local variable and use it within one thread. When you are ready, you must Safely Publish that variable. After that point, all the normal thread safe mechanisms must apply.

Community
  • 1
  • 1
markspace
  • 10,621
  • 3
  • 25
  • 39
0

Yes, local variables are thread safe because the are allocated in the stack. Threads, however, don't share the stack. They are unique for each variable.

Avi
  • 507
  • 3
  • 9
  • 26
0

shared is thread safe, its the state of the object it refers to thats not safe.

It's possible your main thread could throw that exception but highly unlikely.

Telling the anonymous thread start() does not necessarily mean the VM/OS will actually start your thread before the next part of the program executes. So your main thread could enter the sleep before the other thread even starts. If it got interrupted from an external event inside that sleep before the thread set the value you could end up with null.

The sleep on the main thread almost positively ensures the anon thread will run before the test of shared.

Think about what would happen if you removed the sleep and checked for null immediately after starting the new thread. On my system shared[0] was null about 50% of the times I ran your program modified to have the sleep removed.

public static void main(String[] args) {
        final Thread mainThread = Thread.currentThread();
        final Integer[] shared = new Integer[1];

        new Thread(new Runnable() {
            public void run() {
                shared[0] = 1;
                mainThread.interrupt();
            }
        }).start();

        if (shared[0] == null)
            System.out.println("ouch");

    }
JJF
  • 2,681
  • 2
  • 18
  • 31
  • @Durandal edited answer per your suggestion. I think the rest of the answer gets to the heart of the issue OP was asking about. – JJF Dec 10 '15 at 22:10
0

The local variables are stored in the stack and not in the heap, so they are thread safe

Maouven
  • 330
  • 5
  • 11