在Java关键部分,我应该同步什么?
在Java中,在代码中声明关键部分的惯用方法如下:
private void doSomething() { // thread-safe code synchronized(this) { // thread-unsafe code } // thread-safe code }
几乎所有的块都在this
同步,但有一个特别的原因吗? 还有其他的可能吗? 有什么最佳做法的什么对象进行同步? (比如Object
私有实例?)
首先请注意下面的代码片段是相同的。
public void foo() { synchronized (this) { // do something thread-safe } }
和:
public synchronized void foo() { // do something thread-safe }
做同样的事情 。 除了代码可读性和风格之外,没有任何一个偏好。
当你做同步的方法或代码块,重要的是要知道你为什么这样做, 什么对象正是你locking,为了什么目的 。
另外请注意,在这种情况下,您希望在客户端同步代码块,其中您要求的监视器(即同步对象)不一定是this
,如下例所示:
Vector v = getSomeGlobalVector(); synchronized (v) { // some thread-safe operation on the vector }
我build议你获得更多关于并发编程的知识,一旦你知道幕后发生了什么,它将会为你提供很多帮助。 您应该查看Java中的并发编程(Concurrent Programming in Java) ,这是一本关于这个主题的好书。 如果您想快速浏览该主题,请查看Java Concurrency @ Sun
正如前面的回答者已经指出的那样,最好的做法是在一个有限范围的对象上进行同步(换句话说,select可以避开的最严格的范围,然后使用它)。尤其是,同步this
是一个坏主意,除非你打算让你的class级的用户获得locking。
但是,如果您select在java.lang.String
上同步,则会出现一个特别难看的情况。 string可以(并且实际上几乎总是)被实现的。 这意味着在ENTIRE JVM中每个相同内容的string在后台都是相同的string。 这意味着,如果您在任何string上进行同步,另一个(完全不同的)代码段也locking在具有相同内容的string上,实际上也会locking您的代码。
我曾经在生产系统中解决了一个死锁问题,并且(非常痛苦地)将死锁跟踪到了两个完全不同的开源软件包,每个开源软件包都在一个内容都是"LOCK"
的String实例上同步。
我尽量避免同步,因为这将允许从外部引用该对象的每个人阻止我的同步。 相反,我创build一个本地同步对象:
public class Foo { private final Object syncObject = new Object(); … }
现在我可以使用该对象进行同步,而不用担心任何人“偷”锁。
为了突出显示,Java中还有ReadWriteLocks,这些ReadWriteLocks被find为java.util.concurrent.locks.ReadWriteLock。
在我的大部分用法中,我将locking分为“阅读”和“更新”。 如果您只是使用同步关键字,则对同一方法/代码块的所有读取将“排队”。 一次只能有一个线程访问该块。
在大多数情况下,如果您只是在阅读,则不必担心并发问题。 当你正在编写的时候,你担心并发更新(导致数据丢失),或者在写入期间(部分更新)读取,你不得不担心。
因此,在multithreading编程期间,读/写locking对我更有意义。
您需要同步一个可以充当互斥锁的对象。 如果当前的实例( 这个引用)是合适的(例如不是一个单例),那么你可以使用它,因为在Java中任何对象都可以作为互斥体。
在其他情况下,如果这些类的实例可能都需要访问相同的资源,则可能需要在几个类之间共享一个Mutex。
这很大程度上取决于您所在的环境以及您正在构build的系统types。 在我见过的大多数Java EE应用程序中,实际上并不需要同步。
就我个人而言,我认为那些坚持认为从不同意或只是很less同步的答案是错误的。 我认为这取决于你的API。 如果你的类是一个线程安全的实现,你如此logging,那么你应该使用this
。 如果同步不是在其公共方法的调用中将该类的每个实例作为一个整体线程安全的,则应该使用私有的内部对象。 可重用的库组件通常属于前一类 – 在禁止用户在外部同步中封装API之前,您必须仔细考虑。
在前一种情况下,使用this
可以以primefaces方式调用多个方法。 一个例子是PrintWriter,你可能想要输出多行(比如说一个堆栈跟踪到控制台/logging器),并保证它们一起出现 – 在这种情况下,它在内部隐藏同步对象的事实是一个真正的痛苦。 另一个这样的例子是同步收集包装 – 在那里你必须同步集合对象本身以迭代; 由于迭代由多个方法调用组成, 因此无法在内部完全保护它。
在后一种情况下,我使用一个普通对象:
private Object mutex=new Object();
然而,看过很多JVM转储和堆栈跟踪说锁是“一个java.lang.Object()的实例”,我不得不说,使用内部类可能通常会更有帮助,如其他人所build议的。
无论如何,这是我的两个值得。
编辑:另一件事,当同步在this
我更喜欢同步的方法,并保持方法非常细化。 我认为它更清晰,更简洁。
Java中的同步通常涉及在同一个实例上同步操作。 在this
同步然后是非常地道的,因为this
是一个共享的引用,可以在一个类中的不同实例方法(或部分)之间自动获得。
使用另一个专门用于locking的引用,例如通过声明和初始化一个私有字段Object lock = new Object()
,这是我从不需要或使用的东西。 我认为只有当你需要在一个对象内的两个或多个非同步资源上进行外部同步时才有用,尽pipe我总是试图将这种情况重构成一个更简单的forms。
无论如何,隐式(同步方法)或显式synchronized(this)
在Java库中也被使用了很多。 这是一个很好的习惯用法,如果适用的话,应该永远是你的第一select。
你同步什么取决于可能与这个方法调用冲突的其他线程可以同步。
如果this
是一个仅由一个线程使用的对象,并且我们正在访问一个在线程之间共享的可变对象,那么一个很好的select就是同步这个对象 – 在this
同步上没有任何意义,因为另一个修改共享对象的线程可能会甚至不知道this
,但确实知道这个对象。
另一方面,如果许multithreading同时调用这个对象的方法,例如,如果我们在一个单例中,那么同步this
是有意义的。
请注意,syncronized方法通常不是最好的select,因为我们在整个方法运行时都保持一个锁。 如果它包含耗时但是线程安全的部分,并且不那么耗时的线程不安全的部分,那么通过该方法进行同步是非常错误的。
几乎所有的块都在这个同步,但有一个特别的原因吗? 还有其他的可能吗?
这个声明同步整个方法。
private synchronized void doSomething() {
这个声明同步了一部分代码块而不是整个方法。
private void doSomething() { // thread-safe code synchronized(this) { // thread-unsafe code } // thread-safe code }
从oracle文档页面
使这些方法同步有两个作用:
首先,同一个对象上的两个同步方法的调用是不可能交错的 。 当一个线程正在执行一个对象的同步方法时,所有其他调用同一对象的同步方法的线程将阻塞(挂起执行),直到第一个线程完成对象。
还有其他的可能吗? 有什么最佳做法的什么对象进行同步? (比如Object的私有实例?)
有许多可能性和替代的同步。 您可以使用高级并发API (自JDK 1.5发布以来可用)使您的代码线程安全,
Lock objects Executors Concurrent collections Atomic variables ThreadLocalRandom
请参阅下面的SE问题了解更多详情:
同步与locking
避免在Java中同步(this)?