避免同(这)在Java?
-
22-07-2019 - |
题
每当一个问题出现在所有关Java同步,一些人们都非常渴望指出, synchronized(this)
应该避免。相反,他们要求,一个锁在一个私人参考。
某些特定的原因是:
- 一些 邪恶的代码 可以偷你的锁 (很流行这个,还有一个"意外的"变体)
- 所有同步的方法,在同一类使用完全相同的锁,这减少了吞吐量
- 你是(不必要的)暴露太多信息
其他人,包括我认为 synchronized(this)
是一个成语,是使用了大量(也在Java库),安全很好理解的。它不应当避免,因为你有一个错误,你不知道什么会在你的多线程的程序。换句话说:如果它是适用的,然后使用它。
我有兴趣看到一些真实世界的例子(没有foobar的东西)在避免锁定 this
是最好的时候 synchronized(this)
还将做的工作。
因此: 你应该始终避免 synchronized(this)
并将其替换的锁在一间私人参考?
一些进一步的信息(更新作为给出的答案):
- 我们正在谈论的实例同步
- 这两个隐(
synchronized
方法)和明确的形式synchronized(this)
被认为 - 如果你的报价Bloch或其他当局的问题,不要离开出来的部分你不喜欢的(例如有效的Java、项目在线安全: 通常它被锁上的实例,但也有例外。)
- 如果你需要粒度在你的锁比其他
synchronized(this)
提供,然后synchronized(this)
不适用,因此那不是问题
解决方案
我会盖的每个点分开。
一些邪恶的代码可以偷你的锁(很流行这个,还有一个 "意外的"变体)
我更担心 不小心.什么它等于是,这个用的
this
是的你流露出的界面,并应记录在案。有时能力的其它代码来使用你的锁是需要的。这是真实的东西喜欢Collections.synchronizedMap
(请参阅如果是的话,为什么不试).所有同步的方法,在同一类使用完全相同的 锁,这减少了吞吐量
这是过于简单化的思想;只是摆脱
synchronized(this)
不会解决问题。适当的步吞吐量将需要更多的思考。你是(不必要的)暴露太多信息
这是一个变种#1.使用
synchronized(this)
是的一部分,你的接口。如果你不想要的/需要这暴露出来,不这样做。
其他提示
嗯,首先应当指出的是:
public void blah() {
synchronized (this) {
// do stuff
}
}
在语义上相当于:
public synchronized void blah() {
// do stuff
}
这是一个原因不去使用 synchronized(this)
.你可能会认为你可以做的东西周围 synchronized(this)
块。通常的原因是为了尝试和避免不得不做的同步检查在所有,这导致各种各样的并发问题,具体地说的 双重检查锁问题, ,这只是表明如何困难,可以做一个相对较简单的检查线程安全的。
私人锁是一个防御机制,这是从来没有一个糟糕的想法。
此外,正如你提到的,私人锁可以控制颗粒度。一组行动的对象可能是完全无关的另一个但是 synchronized(this)
会互相排斥的访问所有的人。
synchronized(this)
只是真的不给你任何东西。
而你正在使用同步(本)你正在使用的类实例作为一个锁本身。这意味着,虽然锁的收购 1线, , 2线 应该等待。
假设下列代码:
public void method1() {
// do something ...
synchronized(this) {
a ++;
}
// ................
}
public void method2() {
// do something ...
synchronized(this) {
b ++;
}
// ................
}
1的方法改变 一个 和方法2的修改变 b, ,并行修改的同一变量,通过两个线应当避免。但同时 thread1 修改 一个 和 thread2 修改 b 它可以执行的,没有任何竞争条件。
不幸的是,上述代码将不允许这样做,因为我们都采用相同的参考锁;这意味着线,即使他们是不是在比赛的条件应该等和明显的代码的牺牲并发的程序。
该方案是使用 2 不同的锁 两个 不同的变量:
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锁而不是一个(lockA 和 lockB 变量 一个 和 b 分别)并作为一个结果,允许更好的并发,另一方面它变得更加复杂,比第一个例子...
虽然我同意不坚持盲目地以教条式规定,"锁偷"的情况似乎如此偏向你吗?一个线程确实可以获得锁上的你的目的"外部"(synchronized(theObject) {...}
),阻止其他线等同步的实例的方法。
如果你不相信恶意代码,考虑这个代码可能来自第三方(例如,如果开发一些种类的应用程序服务器)。
对"意外"的版本似乎不太可能,但是,正如他们所说,"使一些白痴-的证据,有人会发明一种更好的白痴"。
所以我同意这取决于-关于什么类-没有学校的想法。
编辑以下eljenso的第3列的评论:
我从来没有经历过的锁偷问题,但这是一个假想的情形:
让我们说的你的系统是一个小的容器,以及象我们正在考虑是的 ServletContext
执行情况。它的 getAttribute
方法必须线的安全,作为背景下的属性是共享数据;所以你宣布它作为 synchronized
.让我们也试想一下,你提供的公共托管服务的基础上你的容器的实施。
我是你的客户和部署我的"好"servlet在你的网站。这种事,我的代码中包含一个呼叫 getAttribute
.
一个黑客,伪装成另一个客户,部署他的恶意servlet在你的网站。它包含以下代码 init
方法:
synchronized (this.getServletConfig().getServletContext()) { while (true) {} }
假设我们共同的servlet上下文(允许的规格只要两个servlet是在同一个虚拟的主机),我呼吁 getAttribute
是锁着的永远.黑客已经取得了一DoS在我servlet。
这次袭击是不可能的,如果 getAttribute
是同步的,在一个私人锁,因为第3-缔约方的代码不能获得这种锁。
我不得不承认的例子是人为的和oversimplistic景如何servlet容器的工作原理,但恕我直言,它证明了这一点。
所以我将使我的设计选择基于安全考虑:将我拥有完全的控制权代码访问的实例?会是什么后果的一个线程的持有锁上的一个实例下去?
似乎有一种不同的协商一致意见的C#和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社会已经吸引超过时间到稍微少的安全,但稍微更简短的版本。
这是不是意味着作为一个挖对爪哇,只是反映我的工作经验,在这两种语言。
它取决于有关情况。
如果只有一个共享的实体或多于一个。
看到完整的工作实例 在这里,
一个小小的介绍。
线和共享的实体
它可能对多线程的访问同一个实体,例如多connectionThreads共享一个单一的队列.由于在线运行,同时有可能是一个机会的压倒一切的一个人数据的另一个可能是一个糟糕的情况。
因此,我们需要一些方法,以确保共享的实体是仅由一个线程的时间。(并发).
同步块
同步()块是一种方法,以确保并行访问的可分享的实体。
第一,一个小的比喻
假设有两个人的P1,P2(线)一个脸盆(共享的实体)内部的一个洗手间,有一个门(锁定)。
现在我们想要一个人使用的盥洗池时间。
一个方法是锁门的P1的时候门是锁着的P2p1等待,直到完成他的工作
P1打开门
然后只p1可以使用的盥洗池。
语法。
synchronized(this)
{
SHARED_ENTITY.....
}
"这个"所提供的固有的锁相关联的类(Java开发计对象类在这样一种方式,每个对象可以作为监控)。上述做法的工作现在只有一个公共实体和多个螺纹(1:N)。
N共享的实体-M线
现在想一种情况时,有两个盆里洗手间,只有一个门。如果我们使用以前的做法,只有p1可以使用一个洗手盆,同时p2会在外面等。这是浪费资源,因为没有一个是使用B2(洗脸盆).
一个明智的做法将是建立一个较小的房间里洗手间,并为他们提供一个门每洗脸盆。在这种方式,P1可以访问B1和P2可以访问B2,反之亦然。
washbasin1;
washbasin2;
Object lock1=new Object();
Object lock2=new Object();
synchronized(lock1)
{
washbasin1;
}
synchronized(lock2)
{
washbasin2;
}
看到更多在线程----> 在这里,
的 java.util.concurrent
包裹已经大大减少的复杂性,我的线的安全码。我只有传闻证据,但大多数的工作,我已经看到了 synchronized(x)
似乎是重新实现锁、信号,或者锁,但使用较低级别的监视器。
铭记这一点,同步使用任何这些机制是类似的同步,在一个内部的对象,而不是泄漏锁。这是有益的,在得你有绝对的把握可以控制入境的监控是由两个或更多线程。
- 让你的数据不可改变的如果这是可能的(
final
变量) - 如果你不能避免突变的共享数据的多个线程,使用高级编程结构[例如颗粒状
Lock
API]
锁定提供了独家使用共用的资源:只有一个线程的时间可以获得锁和所有存取共享资源需要,锁可以获取的第一个。
样本代码用 ReentrantLock
它实现了 Lock
接口
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
优点锁定在同步(这个)
使用的同步方法或陈述,部队的所有锁获取和释放发生于块结构的方式。
锁的实现提供额外的功能使用的同步方法和声明提供
- 非阻止试图获得锁(
tryLock()
) - 试图获取锁,可以中断(
lockInterruptibly()
) - 试图获取锁,可以超时(
tryLock(long, TimeUnit)
).
- 非阻止试图获得锁(
锁定类也可以提供行为和语义是不同的隐含的监测锁定,例如
- 保证排序
- 非重新入用
- 僵局的检测
看看这本身的问题有关的各种类型的 Locks
:
你可以实现的线安全通过使用先进的并发API而不是Synchronied块。这个文件 页面 提供了很好的编程结构达到线的安全。
锁定对象 支持锁定的惯用语,简化许多并发的应用程序。
遗嘱执行人 定义一个高级别API为启动和管理的螺纹。执行者的实现提供由java。工具.并行提供的线的游泳池管理适用于大规模的应用。
并发集合 使其更易于管理大型集的数据,并且可以大大减少需要同步。
原子变量 有的功能,尽量减少同步,并有助于避免存储器的一致性错误。
ThreadLocalRandom (在JAVA7)提供了有效率地产生的伪随机数字从多线程。
请参阅 java。工具.并发 和 java。工具.并行。原子 软件包也用于其他方案结构。
如果你已经决定:
- 你的事情需要做的就是锁上的 目前的对象;和
- 你想要的 锁定它与粒度小于 一个整体的方法;
然后我没有看到一个禁忌话过synchronizezd(此)。
有些人蓄意使用同步(本)(而不是标记的方法同步的)内的全部内容的一个方法,因为他们认为这是"更明确的读者",其对象实际上是同步的。因此,只要人们作出明智的选择(例如理解,通过这样做,他们实际上插入额外的字节到的方法,这可能具有连锁效应的潜在优化),我特别不见的一个问题与这一点。你总是应该文件的并行行为的程序,所以我没看见"'同'出版的行为"的说法作为正在以令人信服的。
作为的问题,其对象锁定你应该使用,我认为没什么不同步的现象 如果此预计将通过逻辑的,你在做什么和怎么您类通常会使用.例如,一个集合,对象,你会在逻辑上期望锁是一般的收集本身。
我认为这是一个很好的解释为什么这些都是至关重要的技术在你的腰带在一本名为Java并发在实践中通过布莱恩戈茨。他使得一点非常清楚的-你必须使用相同的锁"到处",以保护的状态下你的对象。同步的方法和同步的对象往往是携手并进的。E.g。矢量同步更新其所有的方法。如果你有一把手为一个向量物体而要做的"放如果不存在",那么仅仅是矢量同步其自己的各个方法是不会保护你免受腐败的国家。你需要同步使用同步(vectorHandle).这将导致在同样的锁被收购的每一个线程,它有一个把手的矢量,并将保护的整体状态矢量。这被称作客户的侧锁。我们知道事实上矢量不同步的(这个)/同步更新其所有的方法和因此同步的对象vectorHandle会的结果在适当同步的矢量对象的状态。它愚蠢到相信你是线的安全,只是因为你使用一线的安全的集合。这恰恰是因ConcurrentHashMap明确介绍了putIfAbsent方法--使这种行动原子.
在摘要
- 同步在法水平允许客户的侧锁。
- 如果你有一个私人的锁定对象-这使得客户的侧锁不可能的。这是好的如果你知道你的班级没有"放如果不存在"种类型的功能。
- 如果你正在设计一个图书馆-然后同步,在这样或同步的方法往往是明智的。因为你很少在一个位置来决定如何类是要被使用。
- 有矢量使用私人锁定对象-就已经不可能得到的"投入,如果不存在"正确的。客户代码将永远不会获得处理私锁从而破坏基本规则的使用完全相同的锁,以保护其国家。
- 同步,在这样或同步的方法确实有一个问题,因为其他人已经指出-有人可能会得到一个锁和从未释放。所有其他线将保留在等待锁被释放。
- 所以知道你在做什么和采取一个正确的。
- 有人认为,有一个私人锁定对象给你更好的粒度-例如如果两个操作无关-他们可以把守不同的锁导致更好的吞吐量。但这个我认为是设计的气味和不码的气味-如果两种行动是完全不相关的为什么是他们的一部分同样的课吗?为什么一个类俱乐部无关的功能吗?可能是一个实用的课吗?嗯-一些工具提供串操纵和日历日期格式通过的同样的实例??...没有任何意义,至少对我来说!!
不,你不应该 总是.然而,我倾向于避免它当有多个关注在一个特定的对象,只需要将在线程安全相对于自己。例如,可能具有的可变数据对象的"标签"和"父母"的领域;需要将这些线程安全,但是改变一个需要不阻止其他被写入/读。(在实践中,我将避免这种通过声明等领域的挥发性和/或使用java。工具.并行的AtomicFoo包装).
同步,在一般的是有点笨拙,因为它打一个很大的封锁而不是思考如何线可能会被允许工作。使用 synchronized(this)
甚至是笨拙和反社会,因为它是在说"没有一个人可以改变 任何东西 在此类的话,我锁定"。你是否经常实际需要做的是什么?
我更希望将有更多的颗粒锁;甚至如果你想停止一切修改(或许你serialising的对象),你可以获得所有的锁,以实现同样的事情,再加上它的更明确的方式。当你使用 synchronized(this)
, ,它不清楚到底为什么你是同步的,或者什么副作用的可能。如果你使用 synchronized(labelMonitor)
, 或甚至更好 labelLock.getWriteLock().lock()
, 很明显你在做什么和什么样的影响的关键部分是有限的。
简短的回答:你必须理解上的差异并做出的选择取决于代码。
只要回答:总的来说,我宁可试着避免 同步(这个) 减少竞争,但私人锁加复杂你必须要知道的。因此使用正确的步的权利的工作。如果你不是那么有经验的多线程,我宁愿坚持实例锁和读了关于这一主题。(就说:只是使用 同步(这个) 并不自动地使类完全线的安全。) 这是不是一个轻松的话题,但一旦你使用它,答案是否使用 同步(这个) 或者不自然。
一个锁是可以用于 可见性 或保护某些数据从 并行的修改 这可能导致种族。
当你需要的只是作出原始种类操作的原子有可用的选择似的 AtomicInteger
和喜欢。
但是假设你们两个整数,这是关系到每一个其他像 x
和 y
负责协调,它们是相互关联的并且应该改变在原子的方式。然后你会保护他们使用相同的锁。
锁定应当只保护国家,是相互关联的。不少和没有更多。如果你使用 synchronized(this)
在每一个方法那么即使国家的类是不相关的所有线将面临竞争,即使更新的无关的状态。
class Point{
private int x;
private int y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
//mutating methods should be guarded by same lock
public synchronized void changeCoordinates(int x, int y){
this.x = x;
this.y = y;
}
}
在上述例子中,我只有一个方法,这两个变异 x
和 y
并不是两个不同的方法 x
和 y
是有关的而且如果我给了两个不同的方法进行变异 x
和 y
别那么它就不会直线的安全。
这个例子就是证明,并不一定方式应当实现的。最好的方式来做到这将是使它 不可改变的.
现在,在反对 Point
例如,有一个例子 TwoCounters
已经提供@安德烈亚斯在那里的状态,这是被保护的两个不同的锁作为国家相互无关。
的过程中采用不同的锁,以保护无关的国家被称为 锁条带或分裂锁
原因不同步 此 是,有时你需要一个以上的锁定(第二锁往往被删除后一些额外的思想,但你还是需要在中间状态)。如果你锁上 此, 你总是要记住这两个锁 此;如果你锁在一间私人目的,该变量名称告诉你。
从读者的角度来看,如果你看到门锁上 此, 你总是要回答这两个问题:
- 什么样的访问是保护 此?
- 是一个锁真的够了,没有人介绍一个错误?
一个例子:
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(...);
...
}
}
如果两个线开始 longOperation()
在两个不同的实例 BadObject
, ,他们获得
他们锁;当是时间的调用 l.onMyEvent(...)
, 我们有一个僵局,因为既没有的螺纹可以获得的其他目的锁。
在这个例子中,我们可以消除的僵局使用两把锁,一个短期操作和一个长久的。
正如已经在这里所说的同步块可以使用户定义的变量为锁定对象,当同步功能只使用"这个"。当然,你可以操纵有领域的功能应当同步等。
但大家都说没有区别之间的同步功能和模块,它涵盖整个功能的使用"这种"锁定的对象。这是不正确的,区别是字节的代码这将产生在这两种情况。在情况下的同步块的使用应该分配给地方变量有参考"这个"。和如此,我们将有一点点大大小功能(不相关的如果你只有少数的功能)。
更详细的解释的差你可以在这里找到:http://www.artima.com/insidejvm/ed2/threadsynchP.html
还使用同步块是不是很好是由于以下观点:
同步的关键词是非常有限的一个领域:在退出一个同步的方框,所有的线,正在等待该锁必须解除,但其中仅有一线得到采取锁;所有其他人看见锁采取和回去阻塞状态。这不仅仅是一个很大的浪费的处理周期:通常的上下文关于解除封锁线也涉及寻呼内存盘,这是非常,非常,价格昂贵。
更多细节,在这一领域,我会推荐你读到这篇文章:http://java.dzone.com/articles/synchronized-considered
这是真的只是补充其他的答案,但如果您的主要反对使用私人的对象锁定的是,它会扰乱你的类的领域,是不相关的业务逻辑,那么项目龙目岛已 @Synchronized
生成的样板,在编写的时间:
@Synchronized
public int foo() {
return 0;
}
编译为
private final Object $lock = new Object[0];
public int foo() {
synchronized($lock) {
return 0;
}
}
一个很好的例子使用的同步(此)。
// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
// some code here...
Set ls;
synchronized(this) {
ls = listeners.clone();
}
for (IListener l : ls) { l.processEvent(event); }
// some code here...
}
正如你可以看到这里,我们使用同步上这个容易的进行合作的lengthly(可能无限循环的运行方法)与一些同步的方法。
当然它可以很容易地改写使用同步在私人领域。但有时,当我们已经有一些设计的同步方法(即遗产阶级,我们来自同(这个)可以唯一的解决方案)。
它取决于任务你想要做,但我不会使用它。此外,检查螺纹-省-ness你想accompish不可能通过同步(这)在首位?也有一些不错的 锁在API 可能会帮助你:)
我只想提到一个可能的解决方案的独特的私人引用原子部件的代码没有依赖性。你可以使用一个静态哈希锁和一个简单的静态的方法名为原子()创建所需要的引用自动使用堆的信息(完整的类的名称和符号)。然后你可以用这种方法在同步声明没有编写新的锁定对象。
// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point
StackTraceElement exepoint = stack[2];
// creates unique key from class name and line number using execution point
String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber());
Object lock = locks.get(key); // use old or create new lock
if (lock == null) {
lock = new Object();
locks.put(key, lock);
}
return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
// start commands
synchronized (atomic()) {
// atomic commands 1
...
}
// other command
}
// Synchronized code
void dosomething2() {
// start commands
synchronized (atomic()) {
// atomic commands 2
...
}
// other command
}
避免使用 synchronized(this)
作为一个锁定机制:这个锁整个类实例并可能导致僵局。在这种情况下,重构的代码锁,只有一个具体方法或可变的,那样整个类不会锁着的。 Synchronised
内部可使用的方法的水平。
而不是使用 synchronized(this)
, 下面的代码显示你怎么可能只是锁定的方法。
public void foo() {
if(operation = null) {
synchronized(foo) {
if (operation == null) {
// enter your code that this method has to handle...
}
}
}
}
我的两个分在2019虽然这个问题已经解决了。
锁定在"这个"不是不好的如果你知道你在做什么,但是,幕后的锁定对'这个'是(不幸的是什么同步的关键词在法定义允许).
如果你真的想用户的类能够'偷你的锁(即防止其他线处理),你实际上想要所有的同步方法,以等待另一个同步的方法是运行的等。应当有意和深思熟虑的关(因此记录下来,以帮助用户理解)。
进一步详细说明,在反向你必须知道你是什么'获得'(或'失去了上)如果锁定在不可锁定(没有人可以偷你的锁,你是在总量控制等等...).
问题为我是同步的关键字的方法的定义签名的使它太容易的程序员 不要认为 关于什么要锁上,这是一个强大的重要的事情要想想如果你不想碰到的问题在多线程序。
一个不能争辩说,'一般',你不要的用户类是能够做到这些东西或是'一般'你想...这取决于功能是编码。你不能让一个拇指规则为你不能预测的所有使用情况。
考虑,例如该printwriter它使用一个内部的锁定的但那人民的斗争使用从多个线程,如果他们不想让他们出来交织。
应你的锁可以访问之外的类或不是你的决定为一个程序员的基础上什么功能的类。它的一部分。你不能搬走,例如同步(这个)同步(provateObjet)在不冒着破坏性的改变在代码中采用它。
注1:我知道你能达到什么同步的(这个)'实现'通过使用一个明确的锁定对象,并揭露它,但我认为这是不必要的,如果你的行为是很好的记录和实际上,你知道什么样的锁定在"这个"装置。
注2:我不同意的说法,如果一些代码,是不小心偷了你的锁定它的一个错误,你必须要解决它。这在某种程度上是相同的参数如说我能让我所有的方法的公即使他们并不是公众。如果某人是'不小心'打电话给我的旨在将私人方法,其一个错误。为什么使这种事故在第一个地方!如果能够偷走你的锁是一个问题您类不允许的。如此简单。
我想点一(其他人使用你的锁)和两(所有方法都使用同样的锁的不必要的)可以发生在任何较大型应用程序。尤其是当没有良好的通信之间的开发。
这是不变的,它主要是一个问题的良好做法和预防错误。