客户端加锁是否违反了同步策略的封装?
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;
}
}
如 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;
}
}