正如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
)会违反同步策略的封装?
答案 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中有所描述,因此这种行为赢了
答案 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;
}
}