有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

多线程对象锁定私有类成员的最佳实践?(爪哇)

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

基本上,在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) 个答案

  1. # 1 楼答案

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

  2. # 2 楼答案

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

    然而,请考虑:

  3. # 3 楼答案

    即使你确信你正在锁定的对象永远不会改变,我发现使用特殊的对象来锁定会更让人放心。它使它更加透明。如果该类将来被其他人显著扩展和/或修改,他可能会找到一个理由,在没有注意到它用于锁定的情况下,将xList设为非最终类。这可能会很快导致问题。线程安全并不是微不足道的,当代码演化时,它可能会变得更加复杂,因此尽可能清晰和安全。与诊断线程安全性问题的成本相比,仅用于锁定的单独对象的成本很小

  4. # 4 楼答案

    就我个人而言,我更喜欢第二种形式。任何其他代码都不能使用该引用(除了反射、调试API等)。您不必担心列表的内部细节是否会尝试在其上同步。(你在列表中调用的任何方法显然都可以访问this,因此可以在列表上进行同步。)你纯粹是在用它来锁定,所以你也把“我是锁”和“我是列表”分开了

    我发现这样更容易对显示器进行推理,因为你可以很容易地看到all使用它的可能代码

    您可能希望创建一个单独的类,纯粹用作监视器,并对toString()进行重写,这有助于诊断。这也会使变量的用途更加明确

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

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