Java中的多个对象锁?

3

在it技术中,是否将锁定对象放在私有字段变量上(而不是使用锁定对象)是安全/可接受的做法?这样,我可以为不同的目的使用不同的锁。以下是示例:

class Test {
  private Integer x = 0;
  private Integer y = 0;

  public void incrementX() {
    synchronized(x) {
      x++;
    }
  }

  public void decrementX() {
    synchronized(x) {
      x++;
    }
  }

  public void incrementY() {
    synchronized(y) {
      y++;
    }
  }

  public void decrementY() {
    synchronized(y) {
      y++;
    }
  }

我应该为每个需要锁定的私有成员拥有一个锁对象吗?例如:

class Test {
  private final Object xLock = new Object();
  private final Object yLock = new Object();
  private Integer x = 0;
  private Integer y = 0;

...

}

我应该只使用一个普通锁来锁定所有需要锁定的私有变量吗?例如:
class Test {
  private final Object objLock = new Object();
  private Integer x = 0;
  private Integer y = 0;

...

}

1
对于基本类型,请考虑使用Atomic*变量,例如AtomicInteger。 - maress
4个回答

7

请注意,始终使用最终成员变量来作为锁!例如,如果您使用一个Integer并且计划更改它,那么这将是非常糟糕的做法,因为每次调用都会看到一个不同的对象并导致数据竞争。

无论您使用一个还是多个锁取决于您想要实现的协调方案,因此它完全是领域特定的。您必须仔细考虑哪些操作是互斥的,哪些不是互斥的,并适当地为它们分配锁。这里没有单一的最佳实践。

如果您的对象有两个正交操作,可能同时发生而不会引起任何数据竞争,那么就需要两个锁。在您的示例中有两个整数,每个整数独立更改。我认为这是两个锁的情况。如果您有更复杂的代码,其中至少一个操作需要访问两个整数,则会将它们绑定在一起,然后您将需要一个单独的锁。


我们不能在原始类型上进行同步吗? - maress
@maress:不行,你不能这样做。监视器是对象的属性,原始类型不是对象,因此它们没有监视器。 - Joachim Sauer
将这个变量声明为final非常重要。问题中的第一个实现无法工作,因为如果一个线程正在改变原始对象,则不同的线程将看到不同的Integer对象。因此,互斥锁将不能按预期工作。第二个实现在这方面更好。 - Andrzej Doyle
@Marko 当然。在我的代码中,我实际上使用了很多私有对象进行同步。 - maress

6
只要这个私有字段是一个对象,锁定它是完全可以接受的。原始类型没有内在锁,因此第一段代码是无效的。
但是,如果这个私有字段可以从外部访问(例如使用getter),则最好避免锁定它,因为这将允许任何人为不同的目的锁定相同的对象。因此,第二种解决方案是最干净的,我个人认为。
使用单个锁是低效的,因为它会阻止应该能够同时运行的方法的并发访问。因此,通常更好的做法是细粒度地锁定。
编辑:
现在你已经改变了问题并使用包装器对象,锁定私有整数实例真的不是一个好的解决方案,因为你在方法内更改这些变量的值。请使用final字段作为锁。
请记住,如果x是Integer实例,则“x ++”等效于:
int temp = x.intValue();
temp++;
x = Integer.valueOf(temp);

此外,由于 Integer.valueOf() 缓存 Integer 实例,可能会导致多个类使用相同的 Integer 实例来锁定完全不同的内容。这将导致执行速度缓慢和死锁问题的产生。

请注意,您实际上并不会“锁定私有字段”。您始终锁定对象(这就是为什么原始类型不起作用的原因)。通常,区分并不重要,但如果字段引用的对象发生更改,则它可能变得非常重要。 - Joachim Sauer

0

我认为你应该为这两个字段分别设置两个不同的锁。你可以锁定对象以防止两个或多个线程同时访问同一个对象。

你还可以看一下Java中的Lock对象 http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/Lock.html。它比synchronize更高效,在java.util.concurrent中还有一些与锁相关的实用类(如果需要,还有一个ReadWriteLock)。


0

据我所知,你使用的锁对象只是一个标识符。我的意思是,你可以使用任何对象。唯一重要的是“如果两个事物必须互斥,则它们必须使用相同的锁定”。

因此,使用自己的变量的方法似乎是可以的。

但是,请记住!!

  • 我认为你不能锁定原始类型,它必须是一个Object
  • 如果你改变字段的值,下一个进程将获取不同的锁定!!!

因此,单独的锁似乎更安全。除非你绝对确定你的字段不会改变(实际上,你应该将其声明为final)。


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