Win a copy of Head First Agile this week in the Agile forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

Happens-before  RSS feed

 
Stephan van Hulst
Saloon Keeper
Posts: 7821
142
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi guys,

I went through the JLS to figure out how the memory model deals with this situation:

Ignoring race conditions, if thread A finishes the call to foo(), it is guaranteed that thread B sees the change to primitive when it tries to print it, right?

  • The assignment to primitive happens-before the increment of atomic.
  • The increment of atomic by A happens-before the read of atomic by B (assuming A finishes its call before B calls bar()).
  • The read of atomic happens-before the printing of primitive.

  • From these it follows that the assignment to primitive happens-before the printing of primitive.

    All of this assumes there is no other synchronization, primitive is not volatile, and there are no race conditions.

    Is it correct to say that the update/read of atomic synchronizes the two threads?
     
    Piet Souris
    Rancher
    Posts: 1984
    67
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    When I read about Threads, I was being told that Threads were allowed to make use of
    a processors private memory cache. And that was why a change in a variable in thread A
    might not be seen by thread B, since B's local copy was not updated. That's what 'volatile'
    was meant for, apart from synchronization issues. Correct or not: this was what I learned.

    I have never been able to translate this simple picture to the current 'happens before'
    relationship that I consider to be a bit vague in places.
    .
    According to my above simple scenario, I'd say that Thread B in your scenario
    still might miss the change of 'primitive', but I agree that there is a 'happens before'
    situation here. So, I really don't know. I would say: make 'primitive' volatile,
    to stay on the safe side.

    But: why do you not consider the race condition here? Isn't that at least as important?

    Anyway: normally I would say: test it!

    But the problem with that is: in the other thread you called me a 'dead horse that you
    did not want to beat' ( ), and to be honest: my non-use of SwingUtilities.invokeLater()
    is not caused by some stubborn opinion of mine, but simply because I tend to forget):
    I've made very much tests, with many threads, to see if I can get some
    memory inconsistency when not doing this invokeLater, and so far I've
    never witnessed any problem whatsoever.
    Moral of this: testing might not guarantee a definite answer.

    Greetz,
    Piet
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 7821
    142
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Piet Souris wrote:That's what 'volatile' was meant for, apart from synchronization issues. Correct or not: this was what I learned.

    Yes, before Java 1.5. Then the meaning of volatile changed. Volatile now is closely linked to the vague 'happens-before' relationship everyone keeps talking about.

    According to my above simple scenario, I'd say that Thread B in your scenario still might miss the change of 'primitive', but I agree that there is a 'happens before' situation here.

    That's the point. Since Java 1.5 changed the memory model, happens-before means that changes are visible. I'm trying to confirm there is a happens-before relationship here.

    I would say: make 'primitive' volatile, to stay on the safe side.

    I don't want to perform synchronization if I don't have to. Accessing a volatile variable incurs half of the cost of a synchronized block.

    But: why do you not consider the race condition here? Isn't that at least as important?

    Yes, but in this question I'm concerned with the memory model, not race conditions. If we replace atomic.get() with a blocking operation that depends on the value of atomic, there is no race condition, but the value of primitive still depends on the memory model.

    Moral of this: testing might not guarantee a definite answer.

    That's why I want to be certain. The reason I urge people to perform Swing operations on the EDT is because I know it can go wrong. In this case, I want to make sure that it can't go wrong.

    P.S. I didn't call you a dead horse ;)
    It's an English expression that I used to refers to the topic we were talking about.
     
    Campbell Ritchie
    Marshal
    Posts: 55793
    164
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    We usually say flogging a dead horse←dictionary link. It says trying to do something impossible. You can beat a horse very gently and get it to walk. You can flog a dead horse as hard as you like and it will never walk.

    I do not remember which thread Stephan used that expression in. As he says, it did not apply to you.
     
    Chris Hurst
    Ranch Hand
    Posts: 443
    3
    C++ Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    1) In your example you use System.out.prinltln every implementation I have seen synchronizes so your code is synchronized ( I appreciate that was not your point ).

    2)
    I don't want to perform synchronization if I don't have to. Accessing a volatile variable incurs half of the cost of a synchronized block.

    There is no guarantee of cost to volatile or the synchronized keyword and indeed in extreme scenarios the JVM would be allowed to optimize them away to nothing (not here).

    When talking about "synchronization" and performance you are interested in three things lack of potential compiler optimization, cache flushes (memory fences) and lock acquisition (often only the first is considered). The JVM authors have put a lot of effort into some excellent optimizations around all three, potential lock acquisition has many tricks to make it less expensive than you'ed think.

    3) The atomic implementation may make use of available CAS (lockless) instructions and the JVM implementation could make use of the fact the actual hardware is more strongly ordered than the weak model Java describes for the code to be portable . Although a processor may/will have caches those caches can and often do communicate with each other (between CPUs) such that much stronger memory ordering is observed than you might expect (e.g. Intel x86 is quite strong). The Java memory model is more like the worst case scenario and would formerly be described as weak.

    Short answer ... just use atomic you'll be fine ;-) there are a whole host of other gotchas around performance with even simple examples eg "false memory sharing" and even old garbage collection I've seen applications grind with too many (badly used) Atomics (GC) .. synchronization can/could be just a flag on a method signature its GC free (not in anyway a normal a reason to not use atomics obviously).
     
    Mike. J. Thompson
    Bartender
    Posts: 689
    17
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Stephan van Hulst wrote:Is it correct to say that the update/read of atomic synchronizes the two threads?


    I believe there is a happens-before relationship between your write to and then read of primitive for the following reasons (all from section 17.4 of the Java Language Specification, The Java Memory Model):

    1) Intra-thread actions are sequentially consistent with program order. Therefore if a comes before b in program order then a happens-before b, thus your write to primitive happens-before your write to atomic.

    2) volatile writes synchronize-with subsequent volatile reads, and any action that synchronizes-with another action also happens-before that action. Therefore the write to atomic happens-before the read of atomic.

    3) The read of atomic happens-before the write of primitive for the same reason mentioned in 1)

    4) The happens-before relationship is transitive.

    if hb(x,y) and hb(y,z) then hb(x,z).

    Therefore your write to primitive happens-before your read of primitive.

     
    Stephan van Hulst
    Saloon Keeper
    Posts: 7821
    142
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Thanks for your valuable input guys. It gave me something to mull over, and going through the specification a couple of more times, I think I have it sorted out.
     
    • Post Reply Bookmark Topic Watch Topic
    • New Topic
    Boost this thread!