--- On Sat, 7/3/10, Adam Heath <doo...@brainfood.com> 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. > > > >