在Java中,在代码中声明关键部分的惯用方式如下:
private void doSomething() {
// thread-safe code
synchronized(this) {
// thread-unsafe code
}
// thread-safe code
}
几乎所有块都在上同步 this
,但是是否有特定原因呢?还有其他可能性吗?关于要同步的对象,是否有最佳实践?(例如Object
?的私有实例)
Answers:
首先,请注意以下代码段是相同的。
public void foo() {
synchronized (this) {
// do something thread-safe
}
}
和:
public synchronized void foo() {
// do something thread-safe
}
做完全一样的事情。除了代码的可读性和样式之外,它们都不是其中之一。
当您同步方法或代码块时,重要的是要知道为什么要这样做,要锁定的对象是什么,目的是什么。
还要注意,在某些情况下,您可能需要客户端同步代码块,而在这些情况下,您要的监视器(即,同步对象)不一定是必须的this
,例如本例:
Vector v = getSomeGlobalVector();
synchronized (v) {
// some thread-safe operation on the vector
}
我建议您了解有关并发编程的更多知识,一旦您确切了解幕后发生的事情,它将为您提供大量帮助。您应该阅读《Java并行编程》,这是一本关于该主题的好书。如果您想快速了解该主题,请查看Java Concurrency @Sun。
正如早期的答复者所指出的,最佳实践是在有限范围的对象上进行同步(换句话说,选择可以使用的限制性最强的范围,并使用它。)特别是,进行同步this
不是一个好主意,除非您打算允许班级的用户获得锁。
但是,如果选择在上进行同步,则会出现一个特别丑陋的情况java.lang.String
。可以(并且实际上几乎总是)将字符串固定。这意味着在ENTIRE JVM中,内容相等的每个字符串在后台都是相同的字符串。这意味着,如果您在任何String上进行同步,则另一个(完全不同的)代码段也锁定具有相同内容的String,实际上也会锁定您的代码。
我曾经对生产系统中的死锁进行故障排除,并且(非常痛苦地)将死锁跟踪到两个完全不同的开源程序包,每个程序包都在String实例(内容均为String)上同步"LOCK"
。
我尝试避免进行同步,this
因为这将允许外部引用该对象的每个人阻止我的同步。相反,我创建了一个本地同步对象:
public class Foo {
private final Object syncObject = new Object();
…
}
现在,我可以使用该对象进行同步,而不必担心有人“偷”锁。
private static class Lock { }; private final Object lock = new Lock();
。
只是为了强调,还有Java中可用的ReadWriteLocks,可以找到java.util.concurrent.locks.ReadWriteLock。
在我的大多数用法中,我将锁定分别为“用于读取”和“用于更新”。如果仅使用synced关键字,则对同一方法/代码块的所有读取都将被“排队”。一次只能有一个线程访问该块。
在大多数情况下,只要阅读即可,您不必担心并发问题。当您在进行写操作时,您会担心并发更新(导致数据丢失)或在写操作期间进行读取(部分更新)。
因此,在多线程编程期间,读/写锁定对我来说更有意义。
就我个人而言,我认为坚持永远不正确或很少正确同步的答案this
是错误的。我认为这取决于您的API。如果您的类是线程安全的实现,因此您要对其进行记录,则应使用this
。如果同步不是要在调用其公共方法时使该类的每个实例成为整个线程安全的对象,则应使用私有内部对象。可重用的库组件通常属于前一类-在禁止用户将API包装在外部同步中之前,必须仔细考虑。
在前一种情况下,usingthis
允许以原子方式调用多种方法。一个示例是PrintWriter,您可能希望在其中输出多行(例如,将堆栈跟踪记录到控制台/记录器)并确保它们一起出现-在这种情况下,它在内部隐藏了同步对象是一个真正的难题。另一个示例是同步的集合包装器-您必须在集合对象本身上进行同步才能进行迭代;由于迭代由多个方法调用组成,因此您不能完全在内部对其进行保护。
在后一种情况下,我使用一个普通对象:
private Object mutex=new Object();
但是,看到许多JVM转储和堆栈跟踪都说锁是“ java.lang.Object()的实例”,我不得不说,使用内部类通常可能会更有帮助,正如其他人所建议的那样。
无论如何,这就是我的两点价值。
编辑:另一件事,在同步时,this
我更喜欢同步方法,并保持方法非常精细。我认为它更清晰,更简洁。
几乎所有块都与此同步,但是是否有特定原因呢?还有其他可能性吗?
该声明使整个方法同步。
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问题:
最佳做法是创建一个仅提供锁的对象:
private final Object lock = new Object();
private void doSomething() {
// thread-safe code
synchronized(lock) {
// thread-unsafe code
}
// thread-safe code
}
通过这样做,您很安全,没有任何调用代码可以通过意外的行使您的方法死锁synchronized(yourObject)
。
(感谢@jared和@ yuval-adam,他们在上面更详细地解释了这一点。)
我的猜测是,this
在教程中使用的流行来自早期的Sun javadoc:https : //docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html