2017-10-12 110 views
2

正如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条款14]一样,客户端锁定违反了对同步策略的封装

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


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

+2

因为'synchronizedList'使用自己作为监视器,而目前情况恰好如此。如果此实现更改,您的代码将无法正常工作。 –

+0

@AndyTurner明白了。通过'ArrayList'监控模式是个好主意。你建议吗? – overexchange

+0

您知道您的实用程序类可以被['synchronizedSet'](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#synchronizedSet(java.util。组))? –

回答

2

您正在依靠synchronizedList将自己用作显示器这一事实,目前情况恰好如此。

你甚至要依赖于该synchronizedList使用​​实现同步,这也恰好是目前真正的事实(这是一个合理的假设,但它不是一个是必要的)。

有些方法可能会改变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描述,所以这种行为不会改变的事实,所以你现在正在做的是精绝;它的设计使用了漏洞抽象,所以如果你从头开始做类似的事情,就不应该这样设计,但是漏洞抽象的属性被记录下来。

+0

为什么'如果实施改变'代码无法正常工作? imho'synchronized(list)'是必须的,否则'Set'-wise操作将不起作用。 –

+0

@ M.leRutte想想如果[这行代码]会发生什么(http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8u40-b25/java/util/ Collections.java#2399)改为'super(list,new Object())'(或['this line'](http://grepcode.com/file/repository.grepcode.com/java/root/jdk/) openjdk/8u40-b25/java/util/Collections.java#2006)改为'this.mutex = new Object()')。互斥体不再是列表本身,所以在'list'上同步将不起作用。 –

+0

啊,是的,如果列表将使用不同于'this'的监视器,并且另一个线程将访问列表,并且示例代码将被执行,那么它将出错。我懂了。无论如何,看到我的答案,这段代码已经很容易出错,因为它需要所有代码才能通过实用程序类。 –

0

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

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

正如我在评论中所写的,使用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; 
    } 
} 
+0

啊,我需要基于位置的元素集合,但也需要'putIfAbsent'。这是一个正确的用法吗? – overexchange

+0

是否会包装['LinkedHashSet'](https://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashSet.html)?保持顺序,但它没有'get(index)',这是我个人很少需要的。 –

+0

上面的使用案例是关于*为现有的线程安全类*添加功能,因此,* putIfAbsent *是今天为现有的“ListHelper ”引入的。 – overexchange