[concurrency-interest] when is safe publication safe?
gregg at cytetech.com
Mon Apr 26 12:39:59 EDT 2010
What you describe, can be done with lighter weight locks, because it is a
"cache" where "eventually" you want the right one. In this discussion, we are
talking about programming language features that change data, which must "appear
changed" at all times "after it changes". This implies that there can't be a
window of "exposure of the change" to me.
A network cache, even if it is locking on every read, will appear much faster
than going across the network. In memory, locked vs not-locked is similar in
relative performance increases for current hardware. What we need, from my
perspective, is more hardware control of this issue where the hardware does the
right thing without software (at least at the application layer) having to worry
about how to make things "work".
The current state of the JVM, to me, is kind of like using core memory in an RFI
environment. Bits can change all around you, continuously and you may never see
exactly what you expect, because the "runtime environment" can not provide any
guarantees about how external forces affect it.
We can all use synchronized on everything, or even declare every variable either
"volatile" or "final". All of this is boiling up around us. There are complete
environments that exists as libraries which have behaviors that work, because of
certain constraints on how they are used, or on how IDEs construct code templates.
I, still think it is best to just work on creating custom "monitor like" API
based classes which wrap up the details of providing "acceleration" of "reads"
and the like, instead of having all this fragile visibility, locking and fences
strewn about in the code. Sure it feels like a waste to create a class for a
single use, but in the end, it really helps compartmentalize the functionality
in a way that you can "fix" and "test" the logic very easily without having to
understand how all of these synchronization and concurrency features are woven
into a particular block of code.
The visibility thing is really the key for me, because the happens-before
injections that can occur when synchronized and other effecting constructs are
used in user code, can really change the "correctness" of software when certain
things that used to happen in a "synchronized" block get pulled out.
For example, lets say that you put logging statements into a synchronized block,
log.fine("State now looks like: "+value );
You put it into the synchronized block, because "value" is atomically related to
the lock, and has no locking, and you need synchronized to "read" its value.
You have now created a happens before "read" on value. Somewhere later in the
code, you have an unlocked read (by mistake, but the code is working) on a
"simple" var that is a member of "value", but not a complex structure. Let's
say, that you run your code this way, and the application has no problems, so
you don't realize that the unlocked read is a problem.
Later, in a code review, someone says "you should conditionalize that logging so
that "value" is not evaluated if logging is off. So, you add
if( log.isLoggable(Level.FINE) )
and suddenly testing reveals that randomly something is not working related to
that "simple" var value.
Through some analysis and debugging, you will eventually discover that the
logging was reading the value of the "simple" var, and that "happens-before" was
creating the "visibility" of the "simple" var and making things work.
It is exactly this kind of very common programming structures that I think have
greatly degraded the chance for Java programmers to be realistically successful
at writing safe, concurrent software structures.
There are a wealth of subtle things that the current visibility issues affect in
often intangible ways. Truly, it takes expert analysis and a lot of effort to
remember all the details of how concurrency really works in Java.
Taylor Gautier wrote:
> This is very interesting. What you have here are read many, write
> infrequently, but writes must be coherent for readers but once
> 'refreshed' readers shouldn't incur barrier penalties.
> Terracotta solves this problem at the network level for many jvms by
> allowing local readers and writers to acquire a lock with a lock lease
> which can be revoked asynchronously. This allows local jvms to proceed
> with a read lock without incurring a lock read penalty. I mention
> Terracotta because for this problem JVMs in a cluster are analogous to
> threads in the JVM.
> Is it possible to construct the same thing in threads using java
> Does a ReentrantReadWriteLock help? I don't think so. What you would
> need is for all threads to acquire and hold a read lock but have an
> ability to acquiesce it on demand for a writer without incurring memory
> barrier hits.
> I can't think of any way to 'cache' a read lock in ThreadLocal such that
> it doesn't incur barriers on every read and yet also can yield to a
> writer asynchronously.
> On Apr 26, 2010, at 2:35 AM, Rémi Forax <
> <mailto:forax at univ-mlv.fr>forax at univ-mlv.fr <mailto:forax at univ-mlv.fr>>
>> Le 26/04/2010 07:17, Joe Bowbeer a écrit :
>>> While I'm accumulating questions...
>>> Why is ThreadLocal not the preferred cache in this case?
>> You can also mutate the metaclass by example by adding a new method
>> in that case, all threads must see the modification.
>> So ThreadLocal doesn't solve the problem here.
>>> On Sun, Apr 25, 2010 at 4:26 PM, Joe Bowbeer wrote:
>>> What you are describing seems like a caching problem as much as
>>> it is about safe publication and/or dynamic languages. The
>>> language runtime creates the (immutable) instances and publishes
>>> them to the cache, right? The performance of the cache is the
>>> hot spot?
>>> So are you using something like MapMaker to implement the cache?
>>> What are you using to hold off a t2 when t1 is in the process of
>>> publishing to the cache? Some scheme involving a Future?
>>> On Sun, Apr 25, 2010 at 8:48 AM, Jochen Theodorou wrote:
>>> Doug Lea wrote:
>>> On 04/25/10 05:31, Jochen Theodorou wrote:
>>> As a first step, consider exactly what
>>> effects/semantics you want
>>> here, and the ways you intend people to be able
>>> to write conditionally
>>> correct Groovy code.
>>> People wouldn't have to write conditionally correct
>>> Groovy code. they
>>> would write normal code as they would in Java (Groovy
>>> and Java are very
>>> It seems implausible that you could do enough
>>> analysis at load/run time to determine whether you need
>>> full locking in the presence of multithreaded racy
>>> vs much cheaper release fences. This would require at
>>> least some
>>> high-quality escape analysis. And the code generated
>>> would differ both for the writing and reading callers.
>>> maybe I did explain it not good. Let us assume I have the
>>> Groovy code:
>>> Then this is really something along the lines of:
>>> and SBA.getMetaClassOf(1) would return the meta class of
>>> Integer. Since this is purely a runtime construct, it does
>>> not exist until the first time this meta class is requested.
>>> So getMetaClassOf would be the place to initialize the meta
>>> class, that would register it in a global structure and on
>>> subsequent invocation use that cached meta class. If two
>>> threads execute the code above, then one would do the
>>> initialization, while the other has to wait. The waiting
>>> thread would then read the initialized global meta class. On
>>> subsequent invocations both threads would just read. Since
>>> changes of the meta class are rare, we would in 99% of all
>>> cases simply read the existing value. Since we have to be
>>> memory aware, these meta class can be unloaded at runtime
>>> too. They are SoftReferenced so it is done only if really
>>> needed. But rather than the normal change a reinitialization
>>> might be needed much more often.
>>> As you see the user code "1+1" does contain zero
>>> synchronization code. The memory barriers are all in the
>>> runtime. It is not that this cannot be solved by using what
>>> Java already has, it is that this is too expensive.
>>> As I mentioned, an alternative is to lay down some rules.
>>> If people stick to the rules they get consistent (in the
>>> of data-race-free) executions, else they might not. And of
>>> such rules, I think the ones that can apply here amount
>>> to saying that other threads performing initializations
>>> trust any of their reads of the partially initialized object.
>>> And further, they cannot leak refs to that object outside
>>> of the
>>> group of initializer threads.
>>> This is not hugely different than the Swing threading rules
>>> but applies only during initialization.
>>> but unlike what the above may suggest there is no single
>>> initialization phase. The meta classes are created on demand.
>>> We cannot know beforehand which meta classes are needed and
>>> doing them all before starting would increase the startup
>>> time big times.
>>> If there were of course a way to recognize a partially
>>> initialized object I could maybe think of something... but is
>>> there a reliable one?
>>> bye blackdrag
>>> Jochen "blackdrag" Theodorou
>>> The Groovy Project Tech Lead ( <http://groovy.codehaus.org>
>>> Concurrency-interest mailing list
>>> <mailto:Concurrency-interest at cs.oswego.edu>Concurrency-interest at cs.oswego.edu <mailto:Concurrency-interest at cs.oswego.edu>
>> Concurrency-interest mailing list
>> <mailto:Concurrency-interest at cs.oswego.edu>Concurrency-interest at cs.oswego.edu
>> <mailto:Concurrency-interest at cs.oswego.edu>
> Concurrency-interest mailing list
> Concurrency-interest at cs.oswego.edu
More information about the Concurrency-interest