每当在SO上出现关于Java同步的问题时,有些人都急于指出应该避免同步(这一点)。相反,他们声称,对私有引用的锁定是首选的。

其中一些原因是:

一些邪恶的代码可能会偷你的锁(这个很流行,也有一个“意外”变体) 同一类中的所有同步方法都使用完全相同的锁,这降低了吞吐量 你(不必要地)暴露了太多信息

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

我感兴趣的是看到一些现实世界的例子(没有foobar的东西),避免锁定在这是可取的,当同步(这)也会做的工作。

因此:您应该总是避免synchronized(this),并将其替换为私有引用上的锁吗?


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

we are talking about instance synchronization both implicit (synchronized methods) and explicit form of synchronized(this) are considered if you quote Bloch or other authorities on the subject, don't leave out the parts you don't like (e.g. Effective Java, item on Thread Safety: Typically it is the lock on the instance itself, but there are exceptions.) if you need granularity in your locking other than synchronized(this) provides, then synchronized(this) is not applicable so that's not the issue


当前回答

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

假设有以下代码:

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


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

方法一修改变量a,方法二修改变量b,应避免两个线程同时修改同一个变量。但是当thread1修改a和thread2修改b时,它可以在没有任何竞争条件的情况下执行。

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

解决方案是对两个不同的变量使用两个不同的锁:

public 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个锁而不是一个锁(分别针对变量a和变量b的lockA和lockB),结果允许更好的并发性,另一方面,它变得比第一个例子更复杂……

其他回答

我对2019年的看法,尽管这个问题本可以已经解决了。

如果你知道你在做什么,锁定'this'并不坏,但在幕后锁定'this'是(不幸的是synchronized关键字在方法定义中允许)。

如果你真的希望你的类的用户能够“窃取”你的锁(即阻止其他线程处理它),你实际上希望所有同步方法在另一个同步方法运行时等待,以此类推。 它应该是有意的、经过深思熟虑的(因此有文档来帮助用户理解它)。

更详细地说,反过来,你必须知道如果你锁定了一个不可访问的锁(没有人可以“偷”你的锁,你完全控制等等),你会“获得”(或“失去”)什么。

对我来说,问题是方法定义签名中的synchronized关键字使程序员很容易不考虑锁定什么,如果你不想在多线程程序中遇到问题,这是一个非常重要的事情。

人们不能争辩说,“通常”你不希望你的类的用户能够做这些事情,或者“通常”你想要……这取决于你编写的是什么功能。因为你不能预测所有的用例,所以你不能制定一个经验法则。

例如,prinwriter使用了一个内部锁,但是如果人们不想让他们的输出相互交错,他们就很难从多个线程中使用它。

锁是否可以在类外部访问是程序员根据类的功能来决定的。它是api的一部分。例如,你不能从synchronized(this)移到synchronized(provateObjet)而不冒破坏使用它的代码更改的风险。

注1:我知道你可以实现任何同步(这个)'实现'通过使用显式锁对象和暴露它,但我认为这是不必要的,如果你的行为是很好的记录,你实际上知道什么锁定'this'的意思。

注2:我不同意这样的观点:如果一些代码不小心偷了你的锁,那就是一个bug,你必须解决它。这在某种程度上等同于说我可以让我所有的方法公开,即使它们本不应该是公开的。如果有人“意外”调用我的意图是私人方法,这是一个bug。为什么会发生这样的事故!!如果能偷你的锁对你的类来说是一个问题,那就不要允许它。就这么简单。

如果你已经决定:

你要做的就是锁定目标 当前对象;而且 你想要 锁定粒度小于 整体方法;

那么我就不认为synchronizezd是一个禁忌。

Some people deliberately use synchronized(this) (instead of marking the method synchronized) inside the whole contents of a method because they think it's "clearer to the reader" which object is actually being synchronized on. So long as people are making an informed choice (e.g. understand that by doing so they're actually inserting extra bytecodes into the method and this could have a knock-on effect on potential optimisations), I don't particularly see a problem with this. You should always document the concurrent behaviour of your program, so I don't see the "'synchronized' publishes the behaviour" argument as being so compelling.

至于应该使用哪个对象的锁的问题,我认为在当前对象上同步并没有什么错,如果这是你所做的逻辑所期望的,以及你的类通常是如何被使用的。例如,对于集合,逻辑上期望锁定的对象通常是集合本身。

我认为第一点(其他人使用您的锁)和第二点(所有方法不必要地使用相同的锁)可能发生在任何相当大的应用程序中。特别是当开发人员之间没有良好的沟通时。

这不是一成不变的,这主要是一个良好的实践和防止错误的问题。

在c#和Java阵营中似乎有不同的共识。我看到的大多数Java代码使用:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

而大多数c#代码选择了更安全的:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

c#语言当然更安全。如前所述,不能从实例外部对锁进行恶意/意外访问。Java代码也有这种风险,但随着时间的推移,Java社区似乎倾向于稍微不那么安全,但稍微更简洁的版本。

这并不是对Java的挖苦,只是我在这两种语言上工作的经验的反映。

不进行同步的原因是,有时您需要多个锁(经过一些额外的思考后,第二个锁通常会被删除,但您仍然需要它处于中间状态)。如果你锁定了这个,你总是要记住两个锁中哪个是这个;如果你锁定一个私有对象,变量名会告诉你。

从读者的角度来看,如果你看到了锁定,你总是必须回答两个问题:

这能保护什么样的权限? 一把锁真的够了吗,难道不是有人引入了漏洞吗?

一个例子:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

如果两个线程在BadObject的两个不同实例上开始longOperation(),它们将获得 他们的锁;当调用l.onMyEvent(…)时,会出现死锁,因为两个线程都不能获得其他对象的锁。

在本例中,我们可以通过使用两个锁来消除死锁,一个用于短操作,一个用于长操作。