每当在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(theObject){…}),阻塞其他线程等待同步实例方法。
如果您不相信恶意代码,请考虑这些代码可能来自第三方(例如,如果您开发了某种应用程序服务器)。
“意外”版本似乎不太可能,但就像他们说的那样,“让一些东西不受白痴的影响,就会有人发明一个更好的白痴”。
所以我同意“这取决于这个班级做什么”的观点。
编辑以下eljenso的前3条评论:
我从来没有遇到过偷锁的问题,但这里有一个想象的场景:
假设您的系统是一个servlet容器,我们考虑的对象是ServletContext实现。它的getAttribute方法必须是线程安全的,因为上下文属性是共享数据;所以你声明它是同步的。让我们再想象一下,您提供了一个基于容器实现的公共托管服务。
我是您的客户,并在您的站点上部署我的“好”servlet。我的代码碰巧包含对getAttribute的调用。
黑客伪装成另一个客户,在您的站点上部署恶意servlet。它在init方法中包含以下代码:
synchronized (this.getServletConfig().getServletContext()) {
while (true) {}
}
假设我们共享相同的servlet上下文(只要两个servlet位于同一个虚拟主机上,规范就允许),那么我对getAttribute的调用将永远锁定。黑客已经在我的servlet上实现了DoS。
如果getAttribute在私有锁上同步,则这种攻击是不可能的,因为第三方代码无法获得此锁。
我承认这个例子是人为设计的,对servlet容器如何工作的看法过于简单,但恕我直言,它证明了这一点。
因此,我将基于安全性考虑做出设计选择:我是否能够完全控制访问实例的代码?线程无限期地持有实例锁的后果是什么?
虽然我同意不要盲目地遵守教条规则,但“偷锁”的场景对你来说是不是很古怪?一个线程确实可以从你的对象“外部”获得锁(synchronized(theObject){…}),阻塞其他线程等待同步实例方法。
如果您不相信恶意代码,请考虑这些代码可能来自第三方(例如,如果您开发了某种应用程序服务器)。
“意外”版本似乎不太可能,但就像他们说的那样,“让一些东西不受白痴的影响,就会有人发明一个更好的白痴”。
所以我同意“这取决于这个班级做什么”的观点。
编辑以下eljenso的前3条评论:
我从来没有遇到过偷锁的问题,但这里有一个想象的场景:
假设您的系统是一个servlet容器,我们考虑的对象是ServletContext实现。它的getAttribute方法必须是线程安全的,因为上下文属性是共享数据;所以你声明它是同步的。让我们再想象一下,您提供了一个基于容器实现的公共托管服务。
我是您的客户,并在您的站点上部署我的“好”servlet。我的代码碰巧包含对getAttribute的调用。
黑客伪装成另一个客户,在您的站点上部署恶意servlet。它在init方法中包含以下代码:
synchronized (this.getServletConfig().getServletContext()) {
while (true) {}
}
假设我们共享相同的servlet上下文(只要两个servlet位于同一个虚拟主机上,规范就允许),那么我对getAttribute的调用将永远锁定。黑客已经在我的servlet上实现了DoS。
如果getAttribute在私有锁上同步,则这种攻击是不可能的,因为第三方代码无法获得此锁。
我承认这个例子是人为设计的,对servlet容器如何工作的看法过于简单,但恕我直言,它证明了这一点。
因此,我将基于安全性考虑做出设计选择:我是否能够完全控制访问实例的代码?线程无限期地持有实例锁的后果是什么?
在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的挖苦,只是我在这两种语言上工作的经验的反映。