--- On Sat, 7/3/10, Adam Heath <[email protected]> wrote:
> Adrian Crum wrote:
> > As the article and the book (Java Concurrency In
> Practice) both point out - the motivation for using DCL (to
> overcome slow speed) is less of an issue (or a non-issue)
> with newer JVMs.
> >
> > I am not a Java expert. But enough Java experts have
> stressed the need to eliminate this anti-pattern that I am
> willing to take their word for it. I said that in
> preparation for repeating what Adam said earlier - there are
> enough potential problems with DCL that it should not be
> used. I agree that there might be some DCL code in OFBiz
> that hasn't caused any problems so far, and it might be
> tempting to think "if it isn't broke, don't fix it" - but
> personally, I prefer to remove the potential problem.
> >
> > I have seen DCL used in two circumstances in OFBiz: to
> lazy load a static field (singleton), and to get elements
> from a Map.
>
> I'd like to to state it now. Do not start removing
> these things. I
> am already working towards that goal. But to do it
> properly, requires
> sufficient multi-threaded testing, and that is going to
> take me a bit.
>
> I currently have 15 separate commits that remove
> synchronization(some
> of those are DCL).
Understood. I don't think anyone is suggesting that we go around with a DCL
hatchet and start chopping out code. From my perspective, we're just discussing
why it shouldn't be used.
>
> >
> > 1. Lazy load a static field
> > ---------------------------
> >
> > // This is bad, don't do this
> > public class MyClass {
> > private static Resource resource;
> >
> > public static Resource getInstance()
> {
> > if (resource == null {
> > synchronized
> (MyClass.class) {
> > if (resource ==
> null) {
> > resource
> = new Resource();
> > }
> > }
> > }
> > return resource;
> > }
> > }
> >
> > The easiest fix in this example is to make
> getInstance() synchronized:
> >
> > public synchronized static Resource getInstance() {
> > if (resource == null {
> > resource = new Resource();
> > }
> > return resource;
> > }
> >
> > The lazy load has been preserved, but at a cost of
> synchronizing every call to getInstance().
> >
> > It would be better to eliminate the lazy load
> altogether:
> >
> > public class MyClass {
> > private static Resource resource =
> new Resource();
> >
> > public static Resource getInstance()
> {
> > return resource;
> > }
>
> You don't need need the accessor here, if you make resource
> final.
>
> > }
>
> This is not the best approach. Now the item is always
> loaded whenever
> MyClass is referenced.
Keep in mind my reply was to Jacques - who wanted to know what the book had to
say on the subject. So, I gave him two examples of safe initialization taken
from the book (section 16.2.3).
>
> class MyClass {
> public static final Resource alwaysNeeded = new
> Resource();
> private static volatile
> AtomicReference<Resource> sometimesNeeded =
> new AtomicReference<Resource>();
> private static volatile
> AtomicReference<Resource> seldomNeeded = new
> AtomicReference<Resource>();
>
> public static Resource getSometimesNeeded() {
> Resource resource = sometimesNeeded.get();
> if (resource != null) return resource;
> resource = new Resource();
> sometimesNeeded.compareAndSet(null,
> resource);
> // second get needed incase 2 threads got
> past the null check
> return sometimesNeeded.get();
> }
> }
>
>
>
> > 2. Get elements from a Map
> > --------------------------
> >
> > // This is bad, don't do this
> > public class MyClass {
> > private static Map<String,
> Resource> resourceMap = FastMap.newInstance();
> >
> > public static Resource
> getResource(String resourceName) {
> > Resource resource =
> resourceMap.get(resourceName);
> > if (resource == null {
> > synchronized
> (MyClass.class) {
> > resource =
> resourceMap.get(resourceName);
> > if (resource ==
> null) {
> > resource
> = new Resource();
> >
> resourceMap.put(resourceName, resource);
> > }
> > }
> > }
> > return resource;
> > }
> > }
> >
> > Again, the easiest fix is to make the getResource
> method synchronized:
> >
> > public synchronized static Resource getResource(String
> resourceName) {
> > Resource resource =
> resourceMap.get(resourceName);
> > if (resource == null {
> > resource = new Resource();
> > resourceMap.put(resourceName,
> resource);
> > }
> > return resource;
> > }
> >
> > This fix comes at a cost of synchronizing every call
> to getResource. If the cost of creating a new instance of
> Resource is low, we can skip synchronization altogether:
> >
> > public static Resource getResource(String
> resourceName) {
> > Resource resource =
> resourceMap.get(resourceName);
> > if (resource == null {
> > resource = new Resource();
> > resourceMap.put(resourceName,
> resource);
> > resource =
> resourceMap.get(resourceName);
> > }
> > return resource;
> > }
>
> This is buggy. Multiple threads could try for the
> same resource,
> create multiple entries, and both call put. The map
> can then become
> corrupted. I've seen it happen. Do not do
> this.
>
> >
> > Using this method, there is a chance that more than
> one thread will create a Resource instance, but we are not
> concerned about that - we just do a second
> resourceMap.get(resourceName) method call to make sure we
> have the Resource instance that "won."
>
> ConcurrentMap map = new ConcurrentHashMap();
>
> map.putIfAbsent(resourceName, resource);
> resource = resourceMap.get(resourceName);
>
> These lines will fix the problem I mentioned.
>
>
>
>