3

前几天我问了一个类似的问题,但对回答不满意,主要是因为我提供的代码有一些人们关注的问题。

基本上,在 Java 中锁定私有成员的最佳实践是什么?假设每个私有字段只能单独操作而不能一起操作(如下面的测试类示例),您应该直接锁定每个私有字段(示例 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

4 回答 4

9

我个人更喜欢第二种形式。根本没有其他代码可以使用该引用(除非反射、调试 API 等)。您无需担心列表的内部细节是否会尝试在其上同步。(您在列表上调用的任何方法显然都可以访问this,因此可以对其进行同步。)您纯粹将其用于锁定 - 因此您还可以分离“我是锁”和“我”之间的关注点我是一个列表”。

我发现这样更容易推理监视器,因为您可以轻松查看使用它的所有可能代码。

您可能希望创建一个单独的类,纯粹用作监视器,并对其进行覆盖,toString()以帮助诊断。这也将使变量的目的更加清晰。

诚然,这种方法确实需要更多的内存,通常你不需要担心代码锁定this......但我个人认为分离关注点并且不必担心代码是否会锁定自身的好处超过了效率成本。如果您发现“浪费”的对象由于某种原因成为性能瓶颈(并且在您分析了类中的代码之后,您可能会同步),您总是可以选择第一种形式

(我个人希望 Java 和 .NET都没有走“每个对象都有一个关联的监视器”路线,但这是另一天的咆哮。)

于 2012-05-13T11:39:55.933 回答
2

示例 1 要好得多。由于xListare final,它们非常适合同步。不需要额外的锁对象,不必要地使代码复杂化并消耗内存。只需确保列表本身永远不会暴露给外界,破坏封装和线程安全。

但是考虑:

于 2012-05-13T11:36:48.133 回答
0

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

于 2012-05-14T12:13:47.930 回答
0

让我们这样说吧:第二种方法使用更多的代码——这些额外的代码给你带来了什么?就并发性而言,两者完全相同,所以从你的应用程序设计的大局来看,它一定是其他方面。

于 2012-05-13T11:59:30.970 回答