100

My teacher in an upper level Java class on threading said something that I wasn't sure of.

He stated that the following code would not necessarily update the ready variable. According to him, the two threads don't necessarily share the static variable, specifically in the case when each thread (main thread versus ReaderThread) is running on its own processor and therefore doesn't share the same registers/cache/etc and one CPU won't update the other.

Essentially, he said it is possible that ready is updated in the main thread, but NOT in the ReaderThread, so that ReaderThread will loop infinitely.

He also claimed it was possible for the program to print 0 or 42. I understand how 42 could be printed, but not 0. He mentioned this would be the case when the number variable is set to the default value.

I thought perhaps it is not guaranteed that the static variable is updated between the threads, but this strikes me as very odd for Java. Does making ready volatile correct this problem?

He showed this code:

public class NoVisibility {  
    private static boolean ready;  
    private static int number;  
    private static class ReaderThread extends Thread {   
        public void run() {  
            while (!ready)   Thread.yield();  
            System.out.println(number);  
        }  
    }  
    public static void main(String[] args) {  
        new ReaderThread().start();  
        number = 42;  
        ready = true;  
    }  
}
Lii
  • 11,553
  • 8
  • 64
  • 88
dontocsata
  • 3,021
  • 3
  • 20
  • 19
  • The visibility of non-local variables does not depend on whether they are static variables, object fields or array elements, they all have the same considerations. (With the problem that array elements can't be made volatile.) – Paŭlo Ebermann Feb 08 '11 at 15:48
  • 1
    ask your teacher what kind of architecture he deems it'd be possible to see '0'. Yet, on theory he is right. – bestsss Feb 11 '11 at 00:09
  • 4
    @bestsss Asking that kind of question would reveal to the teacher that he'd missed the entire point of what he was saying. The point is that competent programmers understand what is guaranteed and what is not and don't rely on things that aren't guaranteed, at least not without understanding precisely what isn't guaranteed and why. – David Schwartz May 03 '15 at 10:48
  • They are shared between everything loaded by the same class loader. Including threads. – user207421 Aug 30 '15 at 10:39
  • Your teacher (and the accepted answer) are 100% right, but I'll mention that it rarely happens--this is the kind of problem that will hide for years and only show itself when it would be most harmful. Even short tests trying to expose the problem tend to act as though everything is fine (Probably because they don't have time for the JVM to do much optimizing), so it's a really good issue to be aware of. – Bill K Apr 27 '18 at 16:05
  • It is very expensive to ensure that all copies are up to date, hence you have to indicate you want that to happen. – Thorbjørn Ravn Andersen Sep 11 '18 at 08:06

7 Answers7

79

There isn't anything special about static variables when it comes to visibility. If they are accessible any thread can get at them, so you're more likely to see concurrency problems because they're more exposed.

There is a visibility issue imposed by the JVM's memory model. Here's an article talking about the memory model and how writes become visible to threads. You can't count on changes one thread makes becoming visible to other threads in a timely manner (actually the JVM has no obligation to make those changes visible to you at all, in any time frame), unless you establish a happens-before relationship.

Here's a quote from that link (supplied in the comment by Jed Wesley-Smith):

Chapter 17 of the Java Language Specification defines the happens-before relation on memory operations such as reads and writes of shared variables. The results of a write by one thread are guaranteed to be visible to a read by another thread only if the write operation happens-before the read operation. The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships. In particular:

  • Each action in a thread happens-before every action in that thread that comes later in the program's order.

  • An unlock (synchronized block or method exit) of a monitor happens-before every subsequent lock (synchronized block or method entry) of that same monitor. And because the happens-before relation is transitive, all actions of a thread prior to unlocking happen-before all actions subsequent to any thread locking that monitor.

  • A write to a volatile field happens-before every subsequent read of that same field. Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.

  • A call to start on a thread happens-before any action in the started thread.

  • All actions in a thread happen-before any other thread successfully returns from a join on that thread.

Community
  • 1
  • 1
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 3
    In practice, "in a timely manner" and "ever" are synonymous. It would be very possible for the above code to never terminate. – TREE Feb 08 '11 at 16:42
  • 4
    Also, this demonstrates another anti-pattern. Don't use volatile to protect more than one piece of shared state. Here, number and ready are two pieces of state, and to update/read them both consistently, you need actual synchronization. – TREE Feb 08 '11 at 16:43
  • Don't forget `final`. final variables are always visible to all threads. – Steve Kuo Feb 08 '11 at 17:50
  • 5
    The part about eventually becoming visible is wrong. Without any explicit happens-before relationship there is no guarantee that any write will _ever_ be seen by another thread, as the JIT is quite within its rights to foist the read into a register and then you'll never see any update. Any eventual load is luck and should not be relied on. – Jed Wesley-Smith Feb 08 '11 at 22:24
  • @Jed Wesley-Smith: You are right, there is no guarantee. I had meant 'eventually' as in 'if you wait long enough' where there is no bound on what long enough is, but it could be clearer. I'll change the answer. – Nathan Hughes Feb 09 '11 at 15:40
  • 2
    "unless you use the volatile keyword or synchronize." should read "unless there is a relevant happens-before relationship between the writer and reader" and this link: http://download.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility – Jed Wesley-Smith Feb 09 '11 at 21:59
  • @Jed Wesley-Smith, Thread.exit guarantees a memory fence, it has a sync. call; and you can see my reply for smth funny. – bestsss Feb 11 '11 at 00:05
  • @TREE, volatile protects nothing, volatile ensures ordering, you need CAS to ensure atomic access and CAS/lock-free algorithms, structures are not an anti-pattern. – bestsss Feb 11 '11 at 06:09
  • 1
    @bestsss Thread.exit doesn't guarantee anything, it has the consequence of making a happens-before edge in its current implementation on the Sun Java6 VM. That is not something you can rely on. – Jed Wesley-Smith Feb 11 '11 at 06:20
  • @Jed Wesley-Smith, I know the spec. but getting the thread removed out the thread group (which is also in the spec) can't ever happen w/o the happens-before part (i.e. a memory fence). – bestsss Feb 11 '11 at 06:24
  • @bestsss link for the spec? I can't seem to find any docs saying this must happen. – Jed Wesley-Smith Feb 12 '11 at 03:42
  • @ Jed Wesley-Smith, ThreadGroup.setDaemon *A daemon thread group is automatically destroyed when its last thread is stopped or its last thread group is destroyed.* You cant define "last" w/o the happens-before. Sun is notorious for not updating docs, though – bestsss Feb 12 '11 at 09:04
  • 2
    @bestsss well spotted. Unfortunately ThreadGroup is broken in so many ways. – Jed Wesley-Smith Feb 13 '11 at 03:46
  • @Jed Wesley-Smith, ofc I know that (the broken part) but well, I find it immensely useful (for OSGi-alike stuff) – bestsss Feb 13 '11 at 09:51
37

He was talking about visibility and not to be taken too literally.

Static variables are indeed shared between threads, but the changes made in one thread may not be visible to another thread immediately, making it seem like there are two copies of the variable.

This article presents a view that is consistent with how he presented the info:

First, you have to understand a little something about the Java memory model. I've struggled a bit over the years to explain it briefly and well. As of today, the best way I can think of to describe it is if you imagine it this way:

  • Each thread in Java takes place in a separate memory space (this is clearly untrue, so bear with me on this one).

  • You need to use special mechanisms to guarantee that communication happens between these threads, as you would on a message passing system.

  • Memory writes that happen in one thread can "leak through" and be seen by another thread, but this is by no means guaranteed. Without explicit communication, you can't guarantee which writes get seen by other threads, or even the order in which they get seen.

...

thread model

But again, this is simply a mental model to think about threading and volatile, not literally how the JVM works.

Community
  • 1
  • 1
Bert F
  • 85,407
  • 12
  • 106
  • 123
13

Basically it's true, but actually the problem is more complex. Visibility of shared data can be affected not only by CPU caches, but also by out-of-order execution of instructions.

Therefore Java defines a Memory Model, that states under which circumstances threads can see consistent state of the shared data.

In your particular case, adding volatile guarantees visibility.

axtavt
  • 239,438
  • 41
  • 511
  • 482
8

They are "shared" of course in the sense that they both refer to the same variable, but they don't necessarily see each other's updates. This is true for any variable, not just static.

And in theory, writes made by another thread can appear to be in a different order, unless the variables are declared volatile or the writes are explicitly synchronized.

biziclop
  • 48,926
  • 12
  • 77
  • 104
5

Within a single classloader, static fields are always shared. To explicitly scope data to threads, you'd want to use a facility like ThreadLocal.

Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
3

When you initialize static primitive type variable java default assigns a value for static variables

public static int i ;

when you define the variable like this the default value of i = 0; thats why there is a possibility to get you 0. then the main thread updates the value of boolean ready to true. since ready is a static variable , main thread and the other thread reference to the same memory address so the ready variable change. so the secondary thread get out from while loop and print value. when printing the value initialized value of number is 0. if the thread process has passed while loop before main thread update number variable. then there is a possibility to print 0

noone
  • 6,168
  • 2
  • 42
  • 51
-2

@dontocsata you can go back to your teacher and school him a little :)

few notes from the real world and regardless what you see or be told. Please NOTE, the words below are regarding this particular case in the exact order shown.

The following 2 variable will reside on the same cache line under virtually any know architecture.

private static boolean ready;  
private static int number;  

Thread.exit (main thread) is guaranteed to exit and exit is guaranteed to cause a memory fence, due to the thread group thread removal (and many other issues). (it's a synchronized call, and I see no single way to be implemented w/o the sync part since the ThreadGroup must terminate as well if no daemon threads are left, etc).

The started thread ReaderThread is going to keep the process alive since it is not a daemon one! Thus ready and number will be flushed together (or the number before if a context switch occurs) and there is no real reason for reordering in this case at least I can't even think of one. You will need something truly weird to see anything but 42. Again I do presume both static variables will be in the same cache line. I just can't imagine a cache line 4 bytes long OR a JVM that will not assign them in a continuous area (cache line).

bestsss
  • 11,796
  • 3
  • 53
  • 63
  • 3
    @bestsss while that is all true today, it relies on current JVM implementation and hardware architecture for its truth, not on the semantics of the program. This means that the program is still broken, even though it may work. It is easy to find a trivial variant of this example that does actually fail in the ways specified. – Jed Wesley-Smith Feb 11 '11 at 06:19
  • 1
    I did say that it doesn't follow the spec, however as a teacher at least find a suitable example that can actually fail on some commodity architecture, so the example is sort of real. – bestsss Feb 11 '11 at 06:26
  • side note: reminds me how `java.util.concurrent.Exchanger` tries to avoid the `fake isolation`(need a link, i know) with bumping the cache line by adding 15 extra longs. – bestsss Feb 11 '11 at 06:34
  • @Software Monkey, where do you see any piece of advice? The post explains what actually happens vs the fictional case given by the teacher, it's easy to write an example that features the problem instead of being solely hypothetical. – bestsss Feb 21 '11 at 01:03
  • 4
    @Bestsss: The simple answer to your question is simply this: "Code to the specification and document, not to the side-effects of your particular system or implementation". This is especially important in a virtual-machine platform designed to be agnostic of the underlying hardware. – Lawrence Dol Feb 21 '11 at 02:33
  • @Software Monkey, perhaps I am being stupid yet I still see no advice, whatsoever. However, I do believe that a teacher shall provide a working example when illustrates any problem since the one given the to OP is virtually broken on any known hardware. If you reread what I say: I never advise to code in a sloppy way, expecting the data will fall in the same cache line. Yet, coding in a way NOT avoiding it (having fake isolation) is a major, major problem. – bestsss Feb 21 '11 at 20:50
  • 1
    @Bestsss: The teachers point is that (a) the code might well work when you test it, and (b) the code is broken because it depends on hardware operation and not the guarantees of the spec. The point is that it looks like it's OK, but it's not OK. – Lawrence Dol Feb 21 '11 at 20:54
  • @Software Monkey, well I said already, it's not difficult to give an example that looks good and it can fail, otherwise a hundred people testing the code for real and never (even once) getting a fluke. Most students rightfully preclude the teacher sucks and move on. – bestsss Feb 21 '11 at 21:16