锁定私有类成员变量 - 最佳实践?(Java)

3
我前几天问了类似的问题,但是对回答并不满意,主要是因为我提供的代码存在一些问题,人们关注这些问题。基本上,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);
    }
  }
}
4个回答

9
个人而言,我更喜欢第二种形式。除了反射、调试API等,没有其他代码可以使用该引用。您不需要担心列表的内部细节是否尝试对其进行同步。(您在列表上调用的任何方法显然都可以访问“this”,因此可以对其进行同步。)您纯粹将其用于锁定-因此,在“我是锁定”和“我是列表”之间也有关注点分离。
我发现这样更容易推理监视器,因为您可以轻松地看到所有可能使用它的代码。
您可能希望创建一个单独的类,纯粹用作监视器,并覆盖toString()以帮助诊断。这也会使变量的目的更清晰。
不可否认,这种方法需要更多的内存,通常情况下您不需要担心代码锁定该对象...但我个人认为将关注点分离并且无需担心该代码是否锁定自身的好处超过了效率成本。如果您发现“浪费”的对象由于某种原因成为性能瓶颈(并且在您潜在要同步的类中分析了代码),则始终可以选择使用第一种形式。
(个人而言,我希望Java和.NET都没有走“每个对象都有关联的监视器”路线,但这是另一天的抱怨。)

2

0

我们这么说吧:第二种方法使用了更多的代码--那个额外的代码给你带来了什么好处?就并发而言,两种方法完全相同,因此它必须是您应用程序设计的整体大局中的其他方面。


0

即使您确定要锁定的对象永远不会更改,我发现使用专门的对象进行锁定更加令人放心。这使其更加透明。如果将来有其他人显着扩展和/或修改类,并且他可能会找到理由使xList非最终版本,而没有注意到它用于锁定。这可能很快导致问题。线程安全并不是微不足道的,当代码发展时可能变得更加复杂,因此请尽可能清晰和安全地进行操作。与诊断线程安全问题的成本相比,仅为锁定单独的对象的成本很小。


同样地,有人可能对类进行修改,导致两个被锁定的对象被一起使用,而没有注意到锁定。这种修改将要求从那时起只使用一个锁,但是有人没有注意到它。我认为这不是一个好借口。正如你所说,最小化风险确实很重要,但是如果你不了解并发方面,那么在进入并发代码时几乎肯定会遇到麻烦。 - Mihai Danila

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接