车身广告报批服务:Client-side Locking, a confuse way fail to lock

来源:百度文库 编辑:九乡新闻网 时间:2024/04/30 01:50:30
Client-side Locking

4.4. Adding Functionality to Existing Thread-safe Classes

The Java class library contains many useful "building block" classes. Reusing existing classes is often preferable to creating new ones: reuse can reduce development effort, development risk (because the existing components are already tested), and maintenance cost. Sometimes a thread-safe class that supports all of the operations we want already exists, but often the best we can find is a class that supports almost all the operations we want, and then we need to add a new operation to it without undermining its thread safety.

As an example, let's say we need a thread-safe List with an atomic put-ifabsent operation. The synchronized List implementations nearly do the job, since they provide the contains and add methods from which we can construct a put-if-absent operation.

The concept of put-if-absent is straightforward enoughcheck to see if an element is in the collection before adding it, and do not add it if it is already there. (Your "check-then-act" warning bells should be going off now.) The requirement that the class be thread-safe implicitly adds another requirementthat operations like put-if-absent be atomic. Any reasonable interpretation suggests that, if you take a List that does not contain object X, and add X twice with put-if-absent, the resulting collection contains only one copy of X. But, if put-if-absent were not atomic, with some unlucky timing two threads could both see that X was not present and both add X, resulting in two copies of X.

The safest way to add a new atomic operation is to modify the original class to support the desired operation, but this is not always possible because you may not have access to the source code or may not be free to modify it. If you can modify the original class, you need to understand the implementation's synchronization policy so that you can enhance it in a manner consistent with its original design. Adding the new method directly to the class means that all the code that implements the synchronization policy for that class is still contained in one source file, facilitating easier comprehension and maintenance.

Another approach is to extend the class, assuming it was designed for extension. BetterVector in Listing 4.13 extends Vector to add a putIfAbsent method. Extending Vector is straightforward enough, but not all classes expose enough of their state to subclasses to admit this approach.

Extension is more fragile than adding code directly to a class, because the implementation of the synchronization policy is now distributed over multiple, separately maintained source files. If the underlying class were to change its synchronization policy by choosing a different lock to guard its state variables, the subclass would subtly and silently break, because it no longer used the right lock to control concurrent access to the base class's state. (The synchronization policy of Vector is fixed by its specification, so BetterVector would not suffer from this problem.)

Listing 4.13. Extending Vector to have a Put-if-absent Method.

@ThreadSafe                        public class BetterVector extends Vector {                        public synchronized boolean putIfAbsent(E x) {                        boolean absent = !contains(x);                        if (absent)                        add(x);                        return absent;                        }                        }                        

4.4.1. Client-side Locking

For an ArrayList wrapped with a Collections.synchronizedList wrapper, neither of these approachesadding a method to the original class or extending the classworks because the client code does not even know the class of the List object returned from the synchronized wrapper factories. A third strategy is to extend the functionality of the class without extending the class itself by placing extension code in a "helper" class.

Listing 4.14 shows a failed attempt to create a helper class with an atomic put-if-absent operation for operating on a thread-safe List.

Listing 4.14. Non-thread-safe Attempt to Implement Put-if-absent. Don't Do this.


@NotThreadSafe                        public class ListHelper {                        public List list =Collections.synchronizedList(new ArrayList());                        
//it not the same lock, synchronizedList of variable list is used list in it's internal synchronized.
//but the client side here is use current this(ListHelper) for lock. so it can prevent the thread safe.
 public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contains(x); if (absent) list.add(x); return absent; } }

Why wouldn't this work? After all, putIfAbsent is synchronized, right? The problem is that it synchronizes on the wrong lock. Whatever lock the List uses to guard its state, it sure isn't the lock on the ListHelper. ListHelper provides only the illusion of synchronization; the various list operations, while all synchronized, use different locks, which means that putIfAbsent is not atomic relative to other operations on the List. So there is no guarantee that another thread won't modify the list while putIfAbsent is executing.

To make this approach work, we have to use the same lock that the List uses by using client-side locking or external locking. Client-side locking entails guarding client code that uses some object X with the lock X uses to guard its own state. In order to use client-side locking, you must know what lock X uses.

The documentation for Vector and the synchronized wrapper classes states, albeit obliquely, that they support client-side locking, by using the intrinsic lock for the Vector or the wrapper collection (not the wrapped collection). Listing 4.15 shows a putIfAbsent operation on a thread-safe List that correctly uses client-side locking.


Listing 4.15. Implementing Put-if-absent with Client-side Locking.

@ThreadSafe                        public class ListHelper {                        public List list =Collections.synchronizedList(new ArrayList());                        public boolean putIfAbsent(E x) {                        synchronized (list)  {                        boolean absent = !list.contains(x);                        if (absent)                        list.add(x);                        return absent;                        }                        }                        }                        

If extending a class to add another atomic operation is fragile because it distributes the locking code for a class over multiple classes in an object hierarchy, client-side locking is even more fragile because it entails putting locking code for class C into classes that are totally unrelated to C. Exercise care when using client-side locking on classes that do not commit to their locking strategy.

Client-side locking has a lot in common with class extensionthey both couple the behavior of the derived class to the implementation of the base class. Just as extension violates encapsulation of implementation [EJ Item 14], client-side locking violates encapsulation of synchronization policy.


Listing 4.16. Implementing Put-if-absent Using Composition.

@ThreadSafe                        public class ImprovedList implements List {                        private final List list;                        public ImprovedList(List list) { this.list = list; }                        public synchronized boolean putIfAbsent(T x) {                        boolean contains = list.contains(x);                        if (contains)                        list.add(x);                        return !contains;                        }                        public synchronized void clear() { list.clear(); }                        // ... similarly delegate other List methods                        }                        
//the lock object now is current this both inner and outer in the list. this is use composition in adapter pattern


ImprovedList adds an additional level of lockingusing its own intrinsic lock. It does not care whether the underlyingList is thread-safe, because it provides its own consistent lockingthat provides thread safety even if the List is not thread-safe orchanges its locking implementation. While the extra layer of synchronization mayadd some small performance penalty,[7] the implementation in ImprovedList isless fragile than attempting to mimic the locking strategy of another object. Ineffect, we've used the Java monitor pattern to encapsulate an existingList, and this is guaranteed to provide thread safety so long as ourclass holds the only outstanding reference to the underlying List.