在Java中避免同步(this)?

问题

每当有关Java同步的问题出现时,有些人非常渴望指出应该避免使用synchronized(this)。相反,他们声称,首选锁定私人参考。

一些给出的原因是:

  • 一些邪恶的代码可能会偷你的锁(这个非常受欢迎,也有一个"意外"的变种)
  • 同一类中的所有同步方法使用完全相同的锁,这会降低吞吐量
  • 你(不必要地)暴露了太多信息

其他人,包括我在内,认为synchronized(this)是一个习惯用法(也在Java库中使用很多),是安全且易于理解的。不应该避免它,因为你有一个错误,你不知道多线程程序中发生了什么。换句话说:如果适用,则使用它。

我有兴趣看到一些现实世界的例子(没有foobar的东西)避免锁定onthis是最好的,当synchronized(this)也可以完成这项工作。

因此:你是否应始终避免使用synchronized(this)并将其替换为私有引用上的锁定?

一些进一步的信息(更新为答案):

  • 我们正在谈论实例同步
  • 考虑隐式(同步方法)和同步(this)的显式形式
  • 如果你引用Bloch或其他有关该主题的权限,请不要遗漏你不喜欢的部分(例如,有效Java,线程安全项目:通常它是实例本身的锁,但也有例外。)
  • 如果你需要锁定粒度而不是synchronized(this)提供的,那么synchronized(this)不适用,所以这不是问题

#1 热门回答(112 赞)

我将分别介绍每一点。

  • 一些邪恶的代码可能会偷你的锁(这个非常受欢迎,也有一个"意外"的变种)我更加担心意外。它相当于使用它是你的类暴露界面的一部分,应该记录下来。有时需要其他代码使用锁的能力。像Collections.synchronizedMap这样的东西也是如此(参见javadoc)。
  • 同一类中的所有同步方法使用完全相同的锁,这会降低吞吐量。这种思维过于简单化;只是摆脱synchronized(this)将无法解决问题。适当的吞吐量同步需要更多考虑。
  • 你(不必要地)暴露太多信息这是#1的变种。使用synchronized(this)是界面的一部分。如果你不想/需要暴露,请不要这样做。

#2 热门回答(81 赞)

那么,首先应该指出:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

在语义上等同于:

public synchronized void blah() {
  // do stuff
}

这是不使用synchronized(this)的一个原因。你可能会争辩说你可以在synchronized(this)块左右做些什么。通常的原因是尝试避免必须进行同步检查,这会导致各种并发问题,特别是double checked-locking problem,它只是表明制作相对简单的检查线程安全是多么困难。

私人锁定是一种防御机制,这绝不是一个坏主意。

此外,正如你所提到的,私有锁可以控制粒度。对象上的一组操作可能与另一组操作完全无关,但是synchronized(this)将相互排除对所有操作的访问。

synchronized(this)我真的不给你任何东西。


#3 热门回答(49 赞)

在使用synchronized(this)时,你正在使用类实例作为锁本身。这意味着当线程1获取锁定时,线程2应该等待

假设以下代码

public void method1() {
    do something ...
    synchronized(this) {
        a ++;      
    }
    ................
}


public void method2() {
    do something ...
    synchronized(this) {
        b ++;      
    }
    ................
}

方法1修改变量和方法2修改变量b,应该避免两个线程同时修改同一个变量。但是,whilethread1modifyingaandthread2modifyingbit可以在没有任何竞争条件的情况下执行。

不幸的是,上面的代码不允许这样做,因为我们对锁使用相同的引用;这意味着线程即使它们不处于竞争状态也应该等待,显然代码牺牲了程序的并发性。

解决方案是使用2个不同的锁来处理不同的变量。

class Test {
        private Object lockA = new Object();
        private Object lockB = new Object();

public void method1() {
    do something ...
    synchronized(lockA) {
        a ++;      
    }
    ................
}


public void method2() {
    do something ...
    synchronized(lockB) {
        b ++;      
    }
    ................
 }

上面的例子使用了更细粒度的锁(2个锁而不是一个(lockAandlockBfor variablesaandbrespectively),结果允许更好的并发性,另一方面它变得比第一个例子更复杂......