使用“this”作为监视器锁定是否始终可接受?

3
例如,我有一个类,在多线程环境下有2个计数器:
public class MyClass {
  private int counter1;
  private int counter2;

  public synchronized void increment1() {
        counter1++;
  }

  public synchronized void increment2() {
        counter2++;
  }

}

有两个不相关的增量操作。但我使用相同的对象进行锁定(this)。

如果客户端同时调用increment1()increment2()方法,那么increment2调用是否会被阻塞,直到increment1()释放this监视器?

如果是真的,这是否意味着我需要为每个操作提供不同的监视器锁(出于性能原因)?


3
@dinesh707 - 这完全是不正确的。原帖作者说的是正确的。 - Brian Roach
只有当JVM确定它们不相互交互时,@dinesh707才能使用。如果它们相互交互,它们必须进行同步。 - John Dvorak
我把它移除了,因为它添加了错误的值。 - dinesh707
7个回答

12
如果客户端同时调用increment1()和increment2()方法,那么如果它们是在同一实例上调用,则increment2()的调用将被阻塞,直到increment1()释放此监视器。如果你需要基于性能考虑为每个操作提供不同的监视器锁,则这是否意味着这是真的?只有您自己了解您的性能要求。在您的实际代码中,这是否真正成为问题?您的实际操作是否持续时间很长?它们是否非常频繁?您是否执行任何诊断来估计其影响?您是否对应用程序进行过分析以找出等待监视器的时间以及无需使用时等待监视器的时间?我实际上建议不要仅出于不同的原因在“this”上同步。当您控制所有内容时,已经很难对线程进行推理,但是当您不知道可以获取监视器的所有内容时,您就会失去一切希望。当您在“this”上同步时,这意味着任何其他引用您对象的代码也可以在同一个监视器上同步。例如,客户端可以使用:
synchronized (myClass) {
    // Do something entirely different
}

这可能会导致死锁、性能问题等各种问题。

如果您在类中使用private final字段,与一个仅用于成为监视器的对象一起创建,那么您就知道获取该监视器的唯一代码将是您自己的代码。


5

1) 是的,increment1()和increment2()互相阻塞是因为它们都在隐式地同步this

2) 如果你需要更好的性能,请考虑使用无锁的java.util.concurrent.atomic.AtomicInteger类。

  private AtomicInteger counter1 = new AtomicInteger();
  private AtomicInteger counter2 = new AtomicInteger();

  public void increment1() {
        counter1.getAndIncrement();
  }

  public void increment2() {
        counter2.getAndIncrement();
  }

2

如果您使用相同的对象,那么它将成为性能瓶颈。您可以为每个计数器使用不同的锁,或者使用 java.util.concurrent.atomic.AtomicInteger 实现并发计数器。

例如:

public class Counter {
  private AtomicInteger count = new AtomicInteger(0);
  public void incrementCount() {
    count.incrementAndGet();
  }
  public int getCount() {
    return count.get();
  }
}

2

如果你在方法上同步,就像你在这里做的一样,你会锁定整个对象,因此两个线程访问该对象的不同变量仍然会相互阻塞。

如果你想一次只同步一个计数器,以便两个线程在访问不同变量时不会互相阻塞,你需要在这里添加两个同步块来处理这两个计数器,并使用不同的变量作为这两个块的“锁”。


你锁定了整个对象 - 这意味着如果我调用对象的长时间同步方法,那么在同步方法停止运行之前,我无法使用该对象? - WelcomeTo
你可以使用这个对象,但是你不能将它作为锁来执行 monitor_enter 操作!因此,对 increment2 的另一个调用(也需要锁定“this”对象)将不得不等待。 - StarPinkER

1
是的,给定的代码与以下代码相同:
  public void increment1() {
        synchronized(this) {
             counter1++;
        }
  }

  public oid increment2() {
        synchronized(this) {
             counter2++;
        }
  }

这意味着同一时间只能执行一个方法。你应该提供不同的锁(在this上进行锁定不是一个好主意),或者其他解决方案。第二个方案是你实际想要的:AtomicInteger

1

如果多个线程尝试调用您的对象上的方法,它们将等待获取锁(尽管不能保证谁获得锁的顺序)。与所有事情一样,在您确定这是代码瓶颈之前,没有理由进行优化。


0
如果您需要从能够同时调用两个操作中获得的性能优势,那么是的,您不需要为不同的操作提供不同的监视器对象。
然而,过早地进行优化也有其缺点,您应该确保在使程序更复杂以适应它之前确实需要它。

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