客户端加锁是否违反了同步策略的封装?

Does client-side locking violates encapsulation of synchronization policy?

Java_author

所述

Client-side locking entails guarding client code that uses some object X with the lock, X uses to guard its own state.


下面代码中的对象 X 是 list。上一点说,使用ListHelper类型对象拥有的锁来同步putIfAbsent(),是错误的锁。

package compositeobjects;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListHelper<E> {

    private List<E> list =
                    Collections.synchronizedList(new ArrayList<E>());


    public boolean putIfAbsent(E x) {
        synchronized(list){
            boolean absent = !list.contains(x);
            if(absent) {
                list.add(x);
            }
            return absent;
        }
    }
}

但是,Java作者说,

Client-side locking has a lot in common with class extension—they 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.

我的理解是,Collections.synchronizedList() return 的嵌套 class 实例也使用 list 对象拥有的锁。


为什么在 ListHelper 中使用客户端锁定(list)违反了同步策略的封装?

你依赖的事实是 synchronizedList 使用自己作为监视器,目前恰好是这样。

你甚至依赖synchronizedList使用synchronized来实现同步这一事实,目前也恰好是这样(这是合理的 假设,但这不是必需的)。

可以通过多种方式更改 synchronizedList 的实现,从而导致您的代码无法正常工作。


例如,the constructor of synchronizedList:

SynchronizedList(List<E> list) {
  super(list);
  // ...
}

可以改为

SynchronizedList(List<E> list) {
  super(list, new Object());
  // ...
}

现在,SynchronizedList 实现中的方法使用的 mutex 字段不再是 this(有效),因此在 list 上的外部同步将不再工作。


话虽如此,使用 synchronized (list) 确实具有预期效果的事实已在 Javadoc 中进行了描述,因此此行为 不会改变,所以你现在做的绝对没问题;它是使用泄漏抽象设计的,因此如果您从头开始做类似的事情,就不应该这样设计,但是记录了泄漏抽象的属性。

您的代码基本上创建了一个同步集。它只添加不在列表中的元素,即 set.

的定义

无论同步列表如何进行自己的锁定,您的代码都必须提供自己的锁定机制,因为有两次调用同步列表,在两次调用之间列表将释放其锁定。因此,如果两个线程要添加相同的对象,它们都可以通过 contains 检查并将其添加到列表中。化合物 synchronize 确保它不是。最重要的是所有列表使用代码都通过您的实用程序class,否则它仍然会失败。

正如我在评论中所写,使用 synchronized set 可以实现完全相同的行为,这也将确保在锁定整个操作时元素尚未添加。通过使用此同步集访问和修改而不使用您的实用程序 class 是可以的。


编辑:

如果您的代码需要一个列表而不是一个集合,并且 LinkedHashSet 不是我会自己创建一个新的同步列表的选项:

public class SynchronizedList<E> implements List<E> {
    private List<E> wrapped = new ArrayList<E>();

    ....
    @override
    public int size() {
       synchronized(this) {
          return wrapped.size();
       }
   }

   ....
   @override
   public void add(E element) {
    synchronized(this) {
        boolean absent = !wrapped.contains(x);
        if(absent) {
            wrapped.add(element);
        }
        return absent;
    }
}