[concurrency-interest] syntax sugar for lazy instantiation

Jed Wesley-Smith jed at atlassian.com
Wed Jun 24 21:41:16 EDT 2009


Ashley Williams wrote:
> Actually there isn't a version of Java6 on the 32 bit mac, which is 
> what I have, but I've now got the java5 docs - this is a great help, 
> thanks.

Ah, yes – bugger! glad to point you in the right direction though...

> I took a look at your LazyReference class and it looks similar to the 
> one suggested by Ben.

It is very similar, the biggest difference is that it is librafied and 
not a pattern for bespoke implementations (and the encapsulation allows 
a few optimisations).

> My requirements for lazy behavior are typically
> because a resource I need isn't available at construction time and not 
> because the result takes a long time to calculate. Given that, would
> you still recommend using future task?

Well, the FutureTask removes the need to manually sync and provides all 
the semantics you require, reducing the . It also neatly handles the 
case where your initialisation function throws an exception or returns 
null without retrying the function (which will be done serially in the 
case of the LazyField example).

> I'm starting to see how ingrained my tendency is to favor space, 
> although this is more because mutable objects are second nature to me
> rather than a deliberate attempt to optimize.

The more work I have done with concurrency, the more I have appreciated 
immutable objects, to the point where we use them exclusively for any 
(new) domain or transfer objects (the only shared mutable state in the 
VM is in caches (caveat, sometimes mutables are incorrectly stored in a 
web session)). Along with inherently safe publication semantics (which 
greatly complicate the implementation of mutable objects and their 
infrastructure) I find it makes _thinking_ concurrently a lot easier – 
state changes are much simpler to understand.

cheers,
jed.

>
> On 24 Jun 2009, at 03:37, Jed Wesley-Smith wrote:
>
>> The Java6 source code can be downloaded as the "Developer 
>> Documentation" from http://developer.apple.com/java/download/
>>
>> We have a LazyReference[1] class used extensively internally that 
>> encapsulates all the required functionality. It is based on 
>> FutureTask as well, and is fully optimised[2] for performance.
>>
>> I am very wary when people try to optimise for space over correctness 
>> and even readability. Concurrency is so hard for most developers that 
>> favouring correctness over space (unless space is proven to be the 
>> first order issue) seems like premature optimisation.
>>
>> cheers,
>> jed.
>>
>> [1] LazyReference from the atlassian-util-concurrent library
>> source: 
>> http://labs.atlassian.com/source/browse/CONCURRENT/trunk/src/main/java/com/atlassian/util/concurrent/LazyReference.java?r=2605 
>>
>> doc: http://labs.atlassian.com/wiki/display/CONCURRENT/LazyReference
>> [2] FutureTask.run() is a no-op if already run, but this is 
>> implemented by a CAS update on the state which can perform worse than 
>> a simple read of the state (isDone()) under certain circumstances.
>>
>> Ashley Williams wrote:
>>> I guess what's making it hard for me to comment on this is that I 
>>> don't have the source code for FutureTask so I have no idea
>>> what it is doing under the hood (thanks Apple). I agree it would be 
>>> nice not to have to introduce a new class and if the
>>> "happy path" is just a null check as with DCL then I would 
>>> definitely see the benefit of this approach.
>>>
>>> The only thing that makes me a little uncomfortable is that I guess 
>>> FutureTask must contain more than one field to record
>>> the exception, callable and result for example. Therefore I would be 
>>> effectively swapping a single field with multiple.
>>> I'm not saying this is a deal breaker though.
>>>
>>> On 23 Jun 2009, at 23:32, Ben Manes wrote:
>>>
>>>> While I understand the performance concern, I have not seen a 
>>>> problem in practice and the locking in FutureTask is well 
>>>> optimized. It provides a nice idiom than sprinkling the code with 
>>>> double-checked locking tricks or introducing a new interface. By 
>>>> using a future you leverage an existing interface and can utilize 
>>>> it to chain lower-level futures for performing post-processing 
>>>> logic on the client. For example...
>>>>
>>>> // written ad-hoc...
>>>> // Retrieves the entry in a remote caching layer where the 
>>>> key/value are use a custom serialization scheme
>>>> // The client's future is bubbled up and performs post-processing 
>>>> to deserialize the value into object form
>>>> // The "lazyFuture" provides the idiom to run the callable, which 
>>>> blocks on the inner future, on a Future#get().
>>>> public <V> Future<V> get(K key) {
>>>> String externalKey = serializer(key);
>>>> final Future<String> future = remoteCache.get(externalKey);
>>>> return Futures.lazyFuture(new Callable<V>() {
>>>> public V call() throws Exception {
>>>> return deserializer(future.get());
>>>> }
>>>> });
>>>> }
>>>>
>>>> Because this idiom is generalized into a custom utility, 
>>>> Futures#lazyFuture(), the implementation can tuned globally if 
>>>> there is a performance problem. Since I haven't experienced any 
>>>> noticeable overhead, the simplest approach for me was to rely on 
>>>> FutureTask until that problem occurs. If a problem later occurs 
>>>> during performance testing, then implementing a Future with 
>>>> double-checked locking is simple, easy to test, and applies 
>>>> globally to all of my usages.
>>>>
>>>> ------------------------------------------------------------------------ 
>>>>
>>>> *From:* Ashley Williams <ashpublic at mac.com <mailto:ashpublic at mac.com>>
>>>> *To:* Ben Manes <ben_manes at yahoo.com <mailto:ben_manes at yahoo.com>>
>>>> *Cc:* concurrency-interest at cs.oswego.edu 
>>>> <mailto:concurrency-interest at cs.oswego.edu>
>>>> *Sent:* Tuesday, June 23, 2009 11:38:44 AM
>>>> *Subject:* Re: [concurrency-interest] syntax sugar for lazy 
>>>> instantiation
>>>>
>>>> I'm going to have to think about this one since I've used futures 
>>>> but never connected them to double checked locks.
>>>> Fo instance I want to check that:
>>>>
>>>> - I get fast access to field 99% of the time
>>>> - I only pay for lock when initializing a field.
>>>>
>>>> I'm sure there are a ton of tricks like this, what would be great 
>>>> is an "effective threading" book for those that have
>>>> read the theory and want to get straight to idioms.
>>>>
>>>> On 23 Jun 2009, at 19:19, Ben Manes wrote:
>>>>
>>>>> I use this trick all the time, but its already supplied via 
>>>>> FutureTask. It has very similar semantics to your code. This trick 
>>>>> can be extended to build lazy maps (and other lazy structures) 
>>>>> which is also very convenient.
>>>>>
>>>>> public class Example {
>>>>> private final FutureTask<Computation> future = new 
>>>>> FutureTask<Computation>(new Computable());
>>>>>
>>>>> // Lazily computes the work and returns the value
>>>>> public Computation getComputation() throws Exception {
>>>>> future.run(); // no-ops on all subsequent calls
>>>>> return future.get();
>>>>> }
>>>>>
>>>>> private static final class Computable implements 
>>>>> Callable<Computation> {
>>>>> public Computation call() {
>>>>> // do work, return
>>>>> }
>>>>> }
>>>>> }
>>>>>
>>>>> ------------------------------------------------------------------------ 
>>>>>
>>>>> *From:* Ashley Williams <ashpublic at mac.com 
>>>>> <mailto:ashpublic at mac.com>>
>>>>> *To:* concurrency-interest at cs.oswego.edu 
>>>>> <mailto:concurrency-interest at cs.oswego.edu>
>>>>> *Sent:* Tuesday, June 23, 2009 10:05:24 AM
>>>>> *Subject:* [concurrency-interest] syntax sugar for lazy instantiation
>>>>>
>>>>> It seems to me that the only reason to use an intrinsic lock on a 
>>>>> lazy getter (stick a synchronized keyword
>>>>> on the method signature) is convenience of syntax. Please correct 
>>>>> me if I am wrong.
>>>>>
>>>>> So is one idea to simplify lazy instantiation, feedback welcome if 
>>>>> anybody can improve on it. It's based
>>>>> on the observation that double checked locking is mostly 
>>>>> boilerplate except for the line that creates
>>>>> the instance and so I've factored it out into a callable interface.
>>>>>
>>>>> Admittedly the anonymous class makes for a more verbose 
>>>>> constructor, but at least the dcl logic is
>>>>> now encapsulated. And if java had support for closures then the 
>>>>> readability problem would go away.
>>>>>
>>>>> So first the class:
>>>>>
>>>>> @ThreadSafe
>>>>> public final class LazyField<T> {
>>>>> private volatile T obj;
>>>>> private final Callable<T> callable;
>>>>>
>>>>> public LazyField(Callable<T> callable) {
>>>>> this.callable = callable;
>>>>> }
>>>>>
>>>>> public T get() {
>>>>> if (obj == null) {
>>>>> synchronized (this) {
>>>>> if (obj == null) {
>>>>> obj = callable.call();
>>>>> }
>>>>> }
>>>>> }
>>>>> return obj;
>>>>> }
>>>>> }
>>>>>
>>>>> and here is an example usage:
>>>>>
>>>>> @ThreadSafe
>>>>> public final class MyClass {
>>>>> private final LazyField<String> message;
>>>>>
>>>>> public MyClass() {
>>>>> this.message = new LazyField<String>(new Callable<String>() {
>>>>> public String call() throws Exception {
>>>>> return "hello world";
>>>>> }
>>>>> });
>>>>> }
>>>>>
>>>>> public String getMessage() {
>>>>> return message.get();
>>>>> }
>>>>> }
>>>>>
>>>>> - Ashley Williams
>>>>>
>>>>>
>>>>
>>>>
>>>>
>>>
>>> ------------------------------------------------------------------------ 
>>>
>>>
>>> _______________________________________________
>>> Concurrency-interest mailing list
>>> Concurrency-interest at cs.oswego.edu
>>> http://cs.oswego.edu/mailman/listinfo/concurrency-interest
>>>
>>
>



More information about the Concurrency-interest mailing list