前几天我问过a similar question,但对回复不满意,主要是因为我提供的代码有一些人们关注的问题.
基本上,在Java中锁定私有成员的最佳做法是什么?假设每个私有字段只能单独操作而不能一起操作(比如下面我的Test类示例),你应该直接锁定每个私有字段(例1),还是应该使用每个私有字段的一般锁定对象来锁定(例2)?
示例1:直接锁定私有字段
class Test {
private final List<Object> xList = new ArrayList<Object>();
private final List<Object> yList = new ArrayList<Object>();
/* xList methods */
public void addToX(Object o) {
synchronized(xList) {
xList.add(o);
}
}
public void removeFromX(Object o) {
synchronized(xList) {
xList.remove(o);
}
}
/* yList methods */
public void addToY(Object o) {
synchronized(yList) {
yList.add(o);
}
}
public void removeFromY(Object o) {
synchronized(yList) {
yList.remove(o);
}
}
}
示例2:每个私有字段使用锁定对象
class Test {
private final Object xLock = new Object();
private final Object yLock = new Object();
private List<Object> xList = new ArrayList<Object>();
private List<Object> yList = new ArrayList<Object>();
/* xList methods */
public void addToX(Object o) {
synchronized(xLock) {
xList.add(o);
}
}
public void removeFromX(Object o) {
synchronized(xLock) {
xList.remove(o);
}
}
/* yList methods */
public void addToY(Object o) {
synchronized(yLock) {
yList.add(o);
}
}
public void removeFromY(Object o) {
synchronized(yLock) {
yList.remove(o);
}
}
}
解决方法:
我个人更喜欢第二种形式.没有其他代码可以使用该引用(禁止反射,调试API等).您无需担心列表的内部详细信息是否尝试在其上进行同步. (你在列表上调用的任何方法显然都可以访问它,因此可以同步它.)你纯粹使用它来锁定 – 所以你也在“我是一个锁”和“我之间分离了关注点列表“.
我发现这样可以更容易推理显示器,因为您可以轻松查看使用它的所有可能代码.
您可能希望创建一个纯粹用作监视器的单独类,并使用toString()的覆盖来帮助诊断.它还可以使变量的目的更清晰.
不可否认,这种方法确实占用了更多的内存,通常你不需要担心代码锁定…但我个人觉得分离问题而不必担心代码是否锁定自身的好处超过了效率成本.如果由于某种原因发现“浪费”的对象是性能瓶颈(并且在分析了可能要同步的类中的代码之后),您总是可以选择第一个表单.
(就我个人而言,我希望Java和.NET都没有“每个对象都有一个相关的监视器”路径,但这是另一天的咆哮.)