对象锁定私有类成员 - 最佳实践? (JAVA)

时间:2012-05-13 11:29:57

标签: java multithreading concurrency locking deadlock

前几天我问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);
    }
  }
}

4 个答案:

答案 0 :(得分:9)

我个人更喜欢第二种形式。没有其他代码可以使用该引用(禁止反射,调试API等)。您无需担心列表的内部详细信息是否尝试在其上进行同步。 (您在列表中调用的任何方法显然都可以访问this,因此可以在其上进行同步。)您纯粹使用它进行锁定 - 因此您也可以分离关注点在“我是一个锁”和“我是一个清单”之间。

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

您可能希望创建一个纯粹用作监视器的单独类,并使用toString()的覆盖来帮助诊断。它还可以使变量的目的更清晰。

不可否认,这种方法 需要更多内存,通常你不需要担心this上的代码锁定...但我个人觉得分离问题而不必担心代码是否锁定自身的好处超过了效率成本。如果您因某种原因发现“浪费”的对象 是性能瓶颈,那么您总是可以选择第一个表单(并且在您分析了类中的代码之后,您可能会去同步)。

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

答案 1 :(得分:2)

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

但请考虑:

答案 2 :(得分:0)

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

答案 3 :(得分:0)

即使你确定你正在进行锁定的对象永远不会改变,我发现使用特殊对象只是为了锁定更令人放心。它使它更透明。如果将来要由其他人对该课程进行大幅度扩展和/或修改,他可能会找到使xList非最终成绩的理由,而不会注意到它用于锁定。这很快就会导致问题。线程安全并非易事,并且在代码发展时会变得更加复杂,因此尽可能清晰且安全。与诊断线程安全问题的成本相比,单独使用锁定对象的成本很小。