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

时间:2017-10-12 08:02:31

标签: java multithreading synchronization encapsulation

正如Java_author所述,

  

客户端锁定需要保护使用带锁的对象X的客户端代码,X用于保护自己的状态。

以下代码中的对象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作者说,

  

客户端锁定与类扩展有许多共同之处 - 它们都将派生类的行为耦合到基类的实现。正如扩展违反了实现的封装[EJ Item 14],客户端锁定违反了同步策略的封装。

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

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

2 个答案:

答案 0 :(得分:3)

您依赖于synchronizedList将自己用作监视器这一事实,目前恰好是真实的。

您甚至依赖于synchronizedList使用synchronized来实现同步的事实,这在目前也恰好是真实的(它是合理假设,但不是必要的。)

有些方法可以改变synchronizedList的实施方式,使您的代码无法正常运作。

例如,the constructor of synchronizedList

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

可以更改为

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

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

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

答案 1 :(得分:0)

您的代码基本上创建了一个同步集。它只添加元素,如果它不在列表中,那就是set的定义。

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

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

编辑:

如果你的代码需要一个列表而不是一个集合,并且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;
    }
}