Java Forum / General / December 2005
Thread Safe
ndxp@hotmail.com - 14 Dec 2005 11:12 GMT class safe {
public static int foo;
public static void setfoo (int x) { foo = x; }
public static void getfoo() { return foo; }
}
I read somewhere, for java the methods setfoo and getfoo are atomic. Does that mean, that I don't have to declared the methods synchronized ?
-t
J. Verdrengh - 14 Dec 2005 11:51 GMT Apparently not
http://www.javaworld.com/javaworld/javaqa/2003-02/01-qa-0214-threadsafe.html
Benji - 14 Dec 2005 17:42 GMT > http://www.javaworld.com/javaworld/javaqa/2003-02/01-qa-0214-threadsafe.html The author does not explain the problem with the following code: Thread1 Thread2 ... MaybeSafe.setFoo(1); MaybeSafe.setFoo(2); ... ... MaybeSafe.getFoo();
If there's no relationship between the two calls to setFoo, then you shouldn't be able to have a guarentee about which one's value will propagate to getFoo. The only way that this can affect the correctness of the program is if there is some dependant relationship between setFoo(1) and setFoo(2) - however, if there is a dependant relationship between them, it would be non-atomic, and would require a synchronized block, which would create a write barrier, and flush the values, and so our "problem" goes away.
He also criticizes java's implementation of get and set properties, but I'm not sure the problem exists there.
I feel like maybe I'm missing something important, since he seems to have far more experience than I do - can anyone explain to me a sitaution in which his example could actually create a problem?
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Jaakko Kangasharju - 15 Dec 2005 07:52 GMT >> http://www.javaworld.com/javaworld/javaqa/2003-02/01-qa-0214-threadsafe.html > [quoted text clipped - 7 lines] > shouldn't be able to have a guarentee about which one's value will > propagate to getFoo. That's exactly the problem that the author describes. Without synchronization in Thread1 and Thread2 you are not guaranteed that the effect of setFoo in Thread1 is ever seen by Thread2.
> The only way that this can affect the correctness of the program is > if there is some dependant relationship between setFoo(1) and > setFoo(2) - however, if there is a dependant relationship between > them, it would be non-atomic, and would require a synchronized > block, which would create a write barrier, and flush the values, and > so our "problem" goes away. You're right, it's not really a safety issue, but more of a liveness issue (safety = nothing undesirable happens, liveness = something desirable happens). Presumably if you call a set method you want the new value to be seen by the whole program, so you need to synchronize (or if you are just getting and setting a single primitive value, make it volatile).
> He also criticizes java's implementation of get and set properties, but > I'm not sure the problem exists there. It is the same thing. You are not guaranteed that the properties set by one thread with setProperties are ever seen by other threads because there is no synchronization.
 Signature Jaakko Kangasharju, Helsinki Institute for Information Technology Every analogy is faulty in exactly the places where the presenter's argument is weakest
Benji - 15 Dec 2005 08:07 GMT > It is the same thing. You are not guaranteed that the properties set > by one thread with setProperties are ever seen by other threads > because there is no synchronization. I think I agree with you as far as what you're saying happens...
however, you're also not guarenteed that the setting thread won't get context switched out right before it sets the properties - and then the system will go for even longer before getting the properties set. So I think the problem that you're describing (which really isn't a problem so much as a small delay, and is unavoidable anyway due to nondeterminism of context switching) is different from the problem that the author is describing, which is that the code is "not thread safe" (his words).
If he thinks that that code is not thread safe, then he can't think that any code that doesn't use a real time scheduler is thread safe.
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Jaakko Kangasharju - 15 Dec 2005 08:38 GMT >> It is the same thing. You are not guaranteed that the properties set >> by one thread with setProperties are ever seen by other threads [quoted text clipped - 3 lines] > context switched out right before it sets the properties - and then the > system will go for even longer before getting the properties set. Of course, you cannot rely on any execution order (unless you implement your own deterministic scheduling). But if the scheduler does not have liveness problems (i.e., if it eventually schedules any ready thread), then you're guaranteed that your setting runs at some point. And when it runs, you need the synchronization to propagate the effects to other threads.
> So I think the problem that you're describing (which really isn't a > problem so much as a small delay, and is unavoidable anyway due to > nondeterminism of context switching) is different from the problem > that the author is describing, which is that the code is "not thread > safe" (his words). I don't think it is different. To me it seems that the author is merely misusing the term "thread safe" to cover both safety and liveness issues. At least that's what I get from his explanation.
 Signature Jaakko Kangasharju, Helsinki Institute for Information Technology It is the programmer's responsibility to write clean code It is the compiler's responsibility to generate efficient code
Benji - 15 Dec 2005 08:51 GMT > implement your own deterministic scheduling). But if the scheduler > does not have liveness problems (i.e., if it eventually schedules any > ready thread), then you're guaranteed that your setting runs at some > point. And when it runs, you need the synchronization to propagate > the effects to other threads. a context switch is the same as a synchronize - the cache gets flushed and all writes are propagated to main memory (which in turn sets the dirty bit on the caches for other threads, and forces them to reload from main memory the next time they access the variable).
So, I still don't think that synchronized fixes anything. (it may make it slightly quicker, but the variable will be synchronized whenever the thread switches for sure - which means that you aren't buying any better guarentees for when the value will be propagated)
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Jaakko Kangasharju - 15 Dec 2005 10:28 GMT > a context switch is the same as a synchronize - the cache gets > flushed and all writes are propagated to main memory (which in turn > sets the dirty bit on the caches for other threads, and forces them > to reload from main memory the next time they access the variable). Okay, this was the issue. Java's memory model does not guarantee this (at least according to Doug Lea's book). Only synchronized and volatile guarantee that changes made by one thread are visible to other threads, thread context switches do not do that.
 Signature Jaakko Kangasharju, Helsinki Institute for Information Technology Do you cry alone, do you cough by yourself?
Roedy Green - 15 Dec 2005 10:35 GMT On Thu, 15 Dec 2005 12:28:53 +0200, Jaakko Kangasharju <jkangash@hiit.fi> wrote, quoted or indirectly quoted someone who said
>> a context switch is the same as a synchronize - the cache gets >> flushed and all writes are propagated to main memory (which in turn >> sets the dirty bit on the caches for other threads, and forces them >> to reload from main memory the next time they access the variable) A context switch does not flush registers to their corresponding variables. If other threads look at those variables, they will be out of date, unless special measures were taken.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Steve Horsley - 16 Dec 2005 01:15 GMT > On Thu, 15 Dec 2005 12:28:53 +0200, Jaakko Kangasharju > <jkangash@hiit.fi> wrote, quoted or indirectly quoted someone who said [quoted text clipped - 7 lines] > variables. If other threads look at those variables, they will be out > of date, unless special measures were taken. Right.
In theory, thread-1 could call setFoo(1) on Wednesday, thread-2 could call setFoo(2) on Thursday, and thread-1 could call getFoo() on Friday and find that it's still 1. This is definitely an issue of timeliness.
Steve
Chris Uppal - 14 Dec 2005 12:27 GMT > I read somewhere, for java the methods setfoo and getfoo are atomic. They are atomic, yes.
> Does > that mean, that I don't have to declared the methods synchronized ? No, it does not mean that. You have to use a synchronised block (or declare the relevant data volatile) or else changes made by one thread are not guaranteed to be seen by any other thread. "Atomic" only means that no thread will see /part/ of the assignment (e.g. the high 16-bits set to the new value, but the low 16-bits still with the old value).
-- chris
ndxp@hotmail.com - 14 Dec 2005 13:26 GMT chris,
can throw more light on this ?
Atomic" only means that no thread
> will see /part/ of the assignment (e.g. the high 16-bits set to the new value, > but the low 16-bits still with the old value). thanks
> > I read somewhere, for java the methods setfoo and getfoo are atomic. > [quoted text clipped - 10 lines] > > -- chris Benji - 14 Dec 2005 22:52 GMT > can throw more light on this ? What chris was talking about could be better explained by this: let's say you have a 'field' that is actually two fields, and you have to set the two fields together. So let's say this is your class:
public class Modulus { private int numerator; private int denominator; public setValue(int numerator, int denominator) { this.numerator = numerator; //thread context switch could happen here this.denominator = denominator; } public double getValue() { return ((double)numerator)/denominator; } }
in the example, you could call setValue, and if the thread switched out at the specified point, the object could be left in an inconsistant state. (the numerator would be set, but the denominator would not - in other words, it would have a value that you never specified)
this can actually happen if you use a long or a double - assignment happens in multiple steps that can have a thread switch happen between the steps.
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Alun Harford - 15 Dec 2005 02:00 GMT >> can throw more light on this ? > [quoted text clipped - 27 lines] > happens > in multiple steps that can have a thread switch happen between the steps. Not it can't. Get an introduction to bytecode, it's very useful stuff.
Alun Harford
Roedy Green - 15 Dec 2005 03:56 GMT On Thu, 15 Dec 2005 02:00:52 +0000 (UTC), "Alun Harford" <alunharford@yahoo.com> wrote, quoted or indirectly quoted someone who said :
>Not it can't. Get an introduction to bytecode, it's very useful stuff. In an interpreted machine with green threads that would be so, but not after the code is converted to machine code with OS threads as it is in hotspot.. You can then get a context switch at any time. Task are completely unaware of when they happen so they can't very well patch things to avoid them except at byte code instruction boundaries.. Tasks have full amnesia of the time they were out unless they check the CPU cycle clock.
Unless Java is doing something very clever, threads are left in an inconsistent state when other threads take over.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
ndxp@hotmail.com - 15 Dec 2005 05:47 GMT I'm still confused ! I don't have much experience in thread programing. Best way, I guess would be to write a "proof of concept" program which should indicate that this little piece of code is thread safe or not. I would like to run this on all JVMs.
> On Thu, 15 Dec 2005 02:00:52 +0000 (UTC), "Alun Harford" > <alunharford@yahoo.com> wrote, quoted or indirectly quoted someone who [quoted text clipped - 15 lines] > Canadian Mind Products, Roedy Green. > http://mindprod.com Java custom programming, consulting and coaching. Benji - 15 Dec 2005 08:01 GMT > I'm still confused ! I don't have much experience in thread programing. > Best way, I guess would be to write a "proof of concept" program which > should indicate that this little piece of code is thread safe or not. I > would like to run this on all JVMs. Actually, that's probably not a good way of testing it. Especially since you probably have a single processor machine, in which case you're never going to see this behavior. Even if you have a dual processor machine (hyperthreading does not count), a P4's memory architecture has stronger guarentees than the java memory model, so you're still not going to see all of the possible problems. Not only that, but actually finding a threading bug could take days of running the same code over and over again.
You're much better off understanding what's going on.
In your case, I seriously doubt that synchronizing will help anything with accessors and modifiers like your original question asked. However, if you don't want to put the time into learning how threading stuff works, it might be better just to slap synchronized around stuff when in doubt - it wont' slow your program down that much unless it's something that's in a really tight loop.
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Roedy Green - 15 Dec 2005 10:28 GMT >I'm still confused ! I don't have much experience in thread programing. >Best way, I guess would be to write a "proof of concept" program which >should indicate that this little piece of code is thread safe or not. I >would like to run this on all JVMs. It is very difficult to debug thread code. You have to become paranoid and simply imagine all possible horrible things that could happen and make sure you have them covered. You can never test enough to cover all possibilities, especially all possibilities on different hardware, platforms, oses.
The keys are:
1. keep the threads from meddling the same variables.
2. don't do raw thread/synchronized/wait/notify code. Use classes that encapsulate that logic. See the concurrent package e.g. Queue
http://mindprod.com/jgloss/queue.html
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Benji - 15 Dec 2005 07:54 GMT > Not it can't. Get an introduction to bytecode, it's very useful stuff. You might not want to go around telling people to get introductions to things that you don't understand yourself, luv. :-*
Just because the bytecode operation is one step doesn't mean that it's going to be translated into one step in the native code that is compiled by the VM.
The Java Memory Model does not guarentee that double and long will be written atomically. See
http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.7
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Alun Harford - 16 Dec 2005 17:30 GMT >> Not it can't. Get an introduction to bytecode, it's very useful stuff. > [quoted text clipped - 9 lines] > > http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.7 Hmm... my apologies. I look silly now.
Alun Harford
Chris Uppal - 15 Dec 2005 10:01 GMT > can throw more light on this ? > > > Atomic" only means that no thread > > will see /part/ of the assignment (e.g. the high 16-bits set to the new > > value, but the low 16-bits still with the old value). I'll try a different tack from that which Benji took. Maybe this will be too low-level to help you, but it's the best I can do.
For a starter, imagine that the Java program is running on an 8-bit machine -- that's to say that the path to main memory ("bus") is 8-bits wide. (Such machines may still exist, but you are unlikely to find yourself using one, this is just the starting point for my example). Since the memory bus can only carry 8 bits at a time, it will take 4 operations (at least) to write one 32-bit int to main store. So if you call: setFoo(0x12345678); then the underlying hardware will execute 4 separate updates of main memory. The exact order is not important, but say it writes out: 0x12 0x34 0x56 0x78 to four consecutive 8-bit locations in main memory. That would effectively copy the (32-bit) integer value into 32 bits of main memory. Now imagine that a different thread on the same machine were attempting to perform: setFoo(0xABABABAB); If that happened at the same time as the other call to setFoo(), then -- unless someone did something special to prevent it -- the resulting four writes of 0xAB could happen /arbitrarily/ interleaved with those of 0x12345667. So in the end the main memory might hold: 0x 12 AB AB 78 The result is that from the point of view of the program, although we had only ever called setFoo() with 0x12345678 and 0xABABABAB, a completely different value could have ended up in the "foo" variable.
Things would be worse if foo was an object reference instead of a 32-bit int. In that case, not only could we end up with a value of "foo" that had never been supplied by setFoo(), but that value could actually be garbage. And if that happened then the very /best/ result you could hope for is that your program would crash immediately.
Now, this example has depended so far on the Java program running on ancient hardware (or very constrained hardware). On more modern machines the bus is 32-bits wide, but the same sort of problem is still possible. That would happen if the memory used to hold "foo" were not aligned on a 32-bit boundary (very unlikely), or if the memory used to hold "x" (the parameter to setFoo()) was not held on a 32-bit boundary (possible). In such cases, the single 32-bit value "x" cannot be transferred to main memory in a single 32-bit write. In practise, on any /specific/ machine, there is a way to ensure that such problems do not actually occur. And that is the point of the "atomic" guarantee in the Java language definition. JVM implementations are /required/ to take whatever steps are necessary to ensure that such problems do not occur.
In a later post in this thread, you asked:
> I'm still confused ! I don't have much experience in thread programing. > Best way, I guess would be to write a "proof of concept" program which > should indicate that this little piece of code is thread safe or not. I > would like to run this on all JVMs. At least as far as this "atomic" issue is concerned, nobody can give you a code snippet that shows the problem -- that's because no legal JVM is allowed to /have/ a problem ;-)
On the other hand, as I mentioned, the "atomic" property (although very valuable for correct programs) isn't itself enough -- not even nearly -- to be able to write thread-safe programs. See my reply to Benji in this thread (a few) for more details.
-- chris
Roedy Green - 15 Dec 2005 10:39 GMT On Thu, 15 Dec 2005 10:01:13 -0000, "Chris Uppal" <chris.uppal@metagnostic.REMOVE-THIS.org> wrote, quoted or indirectly quoted someone who said :
> And that is the point of the "atomic" >guarantee in the Java language definition. are non-volatile long writes guaranteed atomic?
IIRC that is not so.
On a Pentium, arranging for longs to be stored atomically with native threads could be expensive. I'm not sure how you would go about it.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Steve Horsley - 16 Dec 2005 01:29 GMT > On Thu, 15 Dec 2005 10:01:13 -0000, "Chris Uppal" > <chris.uppal@metagnostic.REMOVE-THIS.org> wrote, quoted or indirectly [quoted text clipped - 9 lines] > On a Pentium, arranging for longs to be stored atomically with native > threads could be expensive. I'm not sure how you would go about it. It is my understanding that writes (and reads) of long values are not guaranteed to be atomic, although all other primitives are (including object references, of course). I can't find where I read that now.
Steve
Thomas Hawtin - 16 Dec 2005 01:53 GMT >> are non-volatile long writes guaranteed atomic? >> [quoted text clipped - 6 lines] > guaranteed to be atomic, although all other primitives are (including > object references, of course). I can't find where I read that now. long and double reads and writes are not guaranteed to be atomic, *except* if they are volatile. However, Sun's JVM for x86 has a bug in that respect. I'm not entirely sure why they don't just make it slow, like trig functions.
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Roedy Green - 16 Dec 2005 04:14 GMT On Fri, 16 Dec 2005 01:59:38 +0000, Thomas Hawtin <usenet@tackline.plus.com> wrote, quoted or indirectly quoted someone who said :
>long and double reads and writes are not guaranteed to be atomic, >*except* if they are volatile. However, Sun's JVM for x86 has a bug in >that respect. I'm not entirely sure why they don't just make it slow, >like trig functions. What I'm curious about is what sort of MASM code does Sun generate to insure a save is atomic if a long is declared volatile? What tools are available to snoop on Sun's dynamically generated machine code?
Is it as primitive as acquiring a lock that all threads use for all 64-bit saves? Please no!
There are possible problems with 32 bit quantities too. For anyone unfamiliar with word tearing, here's a good explanation taken from David R. Butenhof's book, Programming with POSIX Threads.
If a variable crosses the boundary between memory units, which can happen if the machine supports unaligned memory access, the computer may have to send the data in two bus transactions. An unaligned 32-bit value, for example, may be sent by writing the two adjacent 32-bit memory units. If either memory unit involved in the transaction is simultaneously written from another processor, half of the value may be lost. This is called "word tearing."
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Thomas Hawtin - 16 Dec 2005 17:52 GMT > On Fri, 16 Dec 2005 01:59:38 +0000, Thomas Hawtin > <usenet@tackline.plus.com> wrote, quoted or indirectly quoted someone [quoted text clipped - 7 lines] > What I'm curious about is what sort of MASM code does Sun generate to > insure a save is atomic if a long is declared volatile? It doesn't for x86. That is the bug.
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4023233
> What tools > are available to snoop on Sun's dynamically generated machine code? IIRC, there is code to dump (dis)assembler produced by HotSpot, but licensing restrictions prevent it being generally available. You favourite debugger should be able to manage. I just tried gdb, but couldn't get it to do anything but whinge.
> Is it as primitive as acquiring a lock that all threads use for all > 64-bit saves? Please no! The slightly more efficient solution is to take a value based upon the hash code of the containing object. Use the bottom few bits as an index into a block of spin locks.
> There are possible problems with 32 bit quantities too. For anyone > unfamiliar with word tearing, here's a good explanation taken from > David R. Butenhof's book, Programming with POSIX Threads. Sun's JVM always aligns objects to eight byte boundaries.
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Roedy Green - 17 Dec 2005 07:20 GMT On Fri, 16 Dec 2005 17:57:53 +0000, Thomas Hawtin <usenet@tackline.plus.com> wrote, quoted or indirectly quoted someone who said :
>> There are possible problems with 32 bit quantities too. For anyone >> unfamiliar with word tearing, here's a good explanation taken from >> David R. Butenhof's book, Programming with POSIX Threads. > >Sun's JVM always aligns objects to eight byte boundaries. but within an object what happens with alignment? Does Sun move all the doubles to the front of the object and 8-byte align them? From what I have been reading about tearing with Intel, they had better align fields.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Roedy Green - 17 Dec 2005 07:24 GMT On Fri, 16 Dec 2005 17:57:53 +0000, Thomas Hawtin <usenet@tackline.plus.com> wrote, quoted or indirectly quoted someone who said :
>> There are possible problems with 32 bit quantities too. For anyone >> unfamiliar with word tearing, here's a good explanation taken from >> David R. Butenhof's book, Programming with POSIX Threads. > >Sun's JVM always aligns objects to eight byte boundaries. I wonder if there is some way to statically analyse code or dynamically monitor code running interpretively to detect potential trouble spots where two different threads look at or write to variables outside sync blocks.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Roedy Green - 16 Dec 2005 04:18 GMT On Fri, 16 Dec 2005 01:59:38 +0000, Thomas Hawtin <usenet@tackline.plus.com> wrote, quoted or indirectly quoted someone who said :
>long and double reads and writes are not guaranteed to be atomic, >*except* if they are volatile. However, Sun's JVM for x86 has a bug in >that respect. I'm not entirely sure why they don't just make it slow, >like trig functions. From the IA-32 Intel Architecture Software Developer's Manual Volume 3 : System Programming Guide
7.1.1 Guaranteed Atomic Operations
The Pentium 4, Intel Xeon, P6 family, Pentium, and Intel486 processors guarantee that the following basic memory operations will always be carried out atomically:
. Reading or writing a byte
. Reading or writing a word aligned on a 16-bit boundary
. Reading or writing a doubleword aligned on a 32-bit boundary
The Pentium 4, Intel Xeon, and P6 family, and Pentium processors guarantee that the following additional memory operations will always be carried out atomically:
. Reading or writing a quadword aligned on a 64-bit boundary.
. 16-bit accesses to uncached memory locations that fit within a 32-bit data bus.
The P6 family processors guarantee that the following additional memory operations will always be carried out atomically:
. Unaligned 16-, 32-, and 64-bit accesses to cached memory that fit within a 32-byte cache line.
Accesses to cacheable memory that are split across bus widths, cache lines and page boundaries are not guaranteed to be atomic by the Pentium 4, Intel Xeon, P6 family, Pentium and Intel486 processors. The Pentium 4, Intel Xeon and P6 family processors provide bus control signals that permit external memory subsystems to make split accesses atomic; however, nonaligned data accesses will seriously impact the performance of the processor and should be avoided.
 Signature Canadian Mind Products, Roedy Green. http://mindprod.com Java custom programming, consulting and coaching.
Benji - 14 Dec 2005 17:32 GMT > class safe {
> public static int foo;
> public static void setfoo (int x) { foo = x; }
> public static void getfoo() { return foo; }
> }
> I read somewhere, for java the methods setfoo and getfoo are atomic. > Does > that mean, that I don't have to declared the methods synchronized ? the methods are atomic in the sense that the only operation they do (reading and writing to a field) is guarenteed to be carried out uninturrupted.
However, "thread safe" is tricky. What a later poster is referring to is the fact that foo can be set by one thread on one processor, and the value will not propagate immediately to the other processor.
however, I disagree with the other poster in that I think this is as thread safe as you can possibly get it - because there is no case in which you would need the value of foo to be immediately propagated across threads in which you were not using a synchronized block, which would propagate the values. I would be very interested if someone could come up with an example where synchronized would help anything as far as correctness of a program goes.
"correct values" are a tricky subject with threads, because it's all a relative thing. usually you go for "the most correct value", with the caveat that "I won't require any better precision". (similar to distributed time protocols)
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Chris Uppal - 15 Dec 2005 09:44 GMT > however, I disagree with the other poster in that I think this is as > thread safe as you can possibly get it - because there is no case in [quoted text clipped - 4 lines] > example where synchronized would help anything as far as correctness of a > program goes. If by "correct" you mean that the program does what the author intended, and what the reader would (presumably) expect, then how about this:
class Worker implements Runnable { boolean m_stopRequested;
public void run() { while (shouldKeepGoing()) oneStepOfWork(); }
public void requestStop() { m_stopRequested = true; }
boolean shouldKeepGoing() { return m_stopRequested; }
private void oneStepOfWork() { //... } }
That defines a simple worker object that can be started as a separate thread, and will continue to run until someone asks it to stop. That can malfunction. It is pretty close to thread-safe -- the assignment to m_stopRequested, and the reads of the same variable, are atomic, so there is no question of garbage appearing in that variable. What's more the variable is a latch (is only ever changed once) so there is no question of race conditions. However, without without synchronisation in requestStop() and shouldKeepGoing(), there is no guarantee that changes to m_stopRequested that are made in one thread will /ever/ be visible to the thread where the worker is running. That counts as incorrect execution in my book.
Just for completeness, this problem could also be fixed by declaring m_stopRequested to be "volatile". That might not be a bad option in this very particular example, but in real world code using explicit synchonisation is usually the only simple and/or sufficient option.
-- chris
Chris Uppal - 15 Dec 2005 10:16 GMT I wrote:
> without without synchronisation in > requestStop() and shouldKeepGoing(), there is no guarantee that changes > to m_stopRequested that are made in one thread will /ever/ be visible to > the thread where the worker is running. That counts as incorrect > execution in my book. Having read you later posts in this thread, I think I should expand this a little. The point is /not/ a liveness issue, it's not that the change might not be propogated across within an appropriate timeframe (for the specific application). The point is that, wthout synchronisation, there is nothing to tell the compiler/JITer that it is not allowed to re-write a loop like:
run() { while (! m_stopRequested) oneStepOfWork(); }
into:
run() { bolean aBool = m_stopRequested. while (! aBool) oneStepOfWork(); }
and that's a correctness issue. The same observation applies at hardware level, although I don't know if there are any JVM implementation that run on hardware (or even if there is any hardware) which defaults to such weak propogation guarantees.
-- chris
Benji - 15 Dec 2005 16:49 GMT > run() > { > while (! m_stopRequested) > oneStepOfWork(); > }
> into:
> run() > { > bolean aBool = m_stopRequested. > while (! aBool) > oneStepOfWork(); > }
> and that's a correctness issue. The same observation applies at hardware > level, although I don't know if there are any JVM implementation that run on > hardware (or even if there is any hardware) which defaults to such weak > propogation guarantees. of course, a loop like that isn't guarenteed not to starve all other processes in the system. =)
in practice, that's bad programming, and if you actually knew that a spinlock would be a good solution, you would also be smart enough to know that you should put a memory barrier inside of the while loop.
However, that's interesting. I didn't think that was a possibility. I was under the impression that the JVM was not allowed to rewrite field accesses like that - only local variables. (I thought that fields automatically had some properties of 'volatile' in that sense) Maybe that's the .NET framework? Can you find a reference for this behavior? I'm looking in the JLS and I can't find anything that mentions it. Just the absence of any mention of it.
 Signature Of making better designs there is no end, and much refactoring wearies the body.
John C. Bollinger - 16 Dec 2005 03:45 GMT >> run() >> { [quoted text clipped - 10 lines] >> oneStepOfWork(); >> } [...]
> in practice, that's bad programming, and if you actually knew that a spinlock > would be a good solution, you would also be smart enough to know that you > should put a memory barrier inside of the while loop. You can't have your cake and eat it too. Your position a few posts ago seems to have been that it should not be necessary to synchronize exclusively for the purpose of ensuring that updates are propagated across threads:
"however, I disagree with the other poster in that I think [atomic reads and writes] is as thread safe as you can possibly get it - because there is no case in which you would need the value of foo to be immediately propagated across threads in which you were not using a synchronized block, which would propagate the values. I would be very interested if someone could come up with an example where synchronized would help anything as far as correctness of a program goes."
Chris responded with just the kind of example you requested, and now you've effectively said "well, duh, you need to synchronize." If you have a point left anywhere in there then I'm missing it.
> However, that's interesting. I didn't think that was a possibility. I was > under the impression that the JVM was not allowed to rewrite field accesses [quoted text clipped - 3 lines] > JLS and I can't find anything that mentions it. Just the absence of any > mention of it. You might want to consider reading chapter 17 of JLS3, especially sections 17.3 and 17.4. These are all about the things that a conforming Java program may do with shared variables. The material probably does not answer your question directly, but it certainly does support Chris' assertion that a program could behave /as if/ it performed the optimization he described.
 Signature John Bollinger jobollin@indiana.edu
Benji - 15 Dec 2005 16:36 GMT > reads of the same variable, are atomic, so there is no question of garbage > appearing in that variable. What's more the variable is a latch (is only ever [quoted text clipped - 3 lines] > /ever/ be visible to the thread where the worker is running. That counts as > incorrect execution in my book.
> Just for completeness, this problem could also be fixed by declaring > m_stopRequested to be "volatile". That might not be a bad option in this very > particular example, but in real world code using explicit synchonisation is > usually the only simple and/or sufficient option. well, 2 things: 1) this is bad code. =) If you're explicitly waiting on the value of a variable to be changed, you should use wait/notify, which uses synchronized, which sould sync the variable. 2) even if you did want to spin-lock, you still have no guarentee that the thread that sets the variable will ever get run. the OS scheduler has the option of freezing this thread indefinitely, just like you have no guarentee that the thread's memory will get synchronized. However, in practice neither of these are concerns.
 Signature Of making better designs there is no end, and much refactoring wearies the body.
John C. Bollinger - 16 Dec 2005 03:04 GMT > 1) this is bad code. =) If you're explicitly waiting on the value of > a variable to be changed, you should use wait/notify, which uses > synchronized, which sould sync the variable. Well, the code isn't guaranteed to work correctly, which was exactly Chris' point. So yes, it's bad code in that sense. On the other hand, wait()/notify() is not correct here, because the thread checking for an update specifically does *not* want to block on the change -- quite the opposite, it wants to keep going until the change signals it to stop. This is pretty much the standard idiom for gracefully stopping a running thread, except that to make it correct you need to synchronize the write and read (on the same monitor) to ensure that any update is seen by the reading thread. This, again, is the point: the atomicity of the read and write are nowhere close to enough to provide thread safety.
> 2) even if you did want to spin-lock, you still have no guarentee that > the thread that sets the variable will ever get run. the OS scheduler > has the option of freezing this thread indefinitely, just like you have > no guarentee that the thread's memory will get synchronized. However, > in practice neither of these are concerns. The thread-scheduling issue is less of a concern, I agree, but I think you are discounting the update visibility much too much.
 Signature John Bollinger jobollin@indiana.edu
Benji - 16 Dec 2005 04:57 GMT > Well, the code isn't guaranteed to work correctly, which was exactly > Chris' point. So yes, it's bad code in that sense. On the other hand, > wait()/notify() is not correct here, because the thread checking for an > update specifically does *not* want to block on the change -- quite the > opposite, it wants to keep going until the change signals it to stop. I think that my point was (which I now realize is incorrect) that if you need to wait on something, the only good way to do it is with a synchronize and a .wait(). However, now I realize that there are several correct situations that could be optimized into incorrect code since the JVM is not aware that a certain bit of code needs to be left as-is.
I think I was perhaps thrown off by the article that made it sound as though the main problem was delay, which is a bit misleading - but I suppose it depends on the application.
Thanks for your responses.
 Signature Of making better designs there is no end, and much refactoring wearies the body.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|