在Java关键部分中,我应该同步什么?

lin*_*lof 70 java multithreading synchronized

在Java中,在代码中声明关键部分的惯用方法如下:

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}
Run Code Online (Sandbox Code Playgroud)

几乎所有的块都同步 this,但是有什么特别的原因吗?还有其他可能吗?是否有关于同步对象的最佳实践?(比如私人实例Object?)

Jar*_*red 65

正如早期的回答者所指出的那样,最佳做法是在有限范围的对象上进行同步(换句话说,选择你可以使用的最严格的范围,并使用它.)特别是,同步this是一个坏主意,除非您打算允许您班级的用户获得锁定.

但是,如果你选择在a上进行同步,则会出现一个特别难看的情况java.lang.String.字符串可以(实际上几乎总是)实习.这意味着在ENTIRE JVM中,每个相同内容的字符串在幕后都是相同的字符串.这意味着如果您同步任何String,另一个(完全不同的)代码部分也会锁定具有相同内容的String,实际上也会锁定您的代码.

我曾经在生产系统中对死锁进行故障排除,并且(非常痛苦地)跟踪两个完全不同的开源软件包的死锁,每个软件包都在一个内容都是String的String实例上同步"LOCK".

  • 关于String实例锁的有用的真实世界轶事+1. (15认同)

Yuv*_*dam 46

首先,请注意以下代码段是相同的.

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}
Run Code Online (Sandbox Code Playgroud)

和:

public synchronized void foo() {
    // do something thread-safe
}
Run Code Online (Sandbox Code Playgroud)

同样的事情.除了代码可读性和样式之外,对它们中的任何一个都没有偏好.

当您同步方法或代码块时,重要的是要知道为什么要做这样的事情,以及您正在锁定什么对象,以及用于何种目的.

另请注意,在某些情况下,您需要客户端同步代码块,其中您要求的监视器(即同步对象)不一定this,如下例所示:

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}
Run Code Online (Sandbox Code Playgroud)

我建议你对并发编程有更多的了解,一旦你确切知道幕后发生了什么,它将为你提供很多帮助.你应该查看Java中的Concurrent Programming,这是一本关于这个主题的好书.如果您想快速了解主题,请查看Java Concurrency @ Sun

  • 最明显的区别是synchronized(this)块编译为比同步方法更长的字节码.编写synchronized方法时,编译器只在方法上放置一个标志,JVM在看到标志时获取锁.当您使用synchronized(this)块时,编译器会生成类似于try-finally块的字节码,该块获取并释放锁并将其内联到您的方法中. (4认同)
  • @jason指向一个关于DCL的文档,因为"它指出编译器或JMM可能会做出意想不到的事情"是过度的,而不是真正的重点.您必须指定为什么它们不相同.大多数人都认为它们是等效的:http://stackoverflow.com/questions/417285 (2认同)
  • 请注意,同步方法通常不是最佳选择,因为我们在方法运行的整个过程中保持锁定.如果它包含耗时但线程安全的部分,以及不那么耗时的线程不安全部分,则同步方法是非常错误的. (2认同)

Bom*_*mbe 39

我试图避免同步,this因为这将允许来自外部的所有参与该对象的人阻止我的同步.相反,我创建了一个本地同步对象:

public class Foo {
    private final Object syncObject = new Object();
    …
}
Run Code Online (Sandbox Code Playgroud)

现在我可以使用该对象进行同步,而不必担心任何人"窃取"锁定.

  • 谁会试图以某种方式"阻止你的同步",以便同步这个会变得危险,不实用或无法使用? (9认同)
  • 使堆栈跟踪更具可读性,您可能希望为锁定对象的类提供一个名称,以显示它是哪个锁:`private static class Lock {}; private final Object lock = new Lock();`. (8认同)
  • 如果您的程序中有错误,请调试.我非常怀疑,如果你已经没有真正了解你的多线程程序中发生的事情的话,避免synchronized(this)会对你有所帮助. (6认同)
  • 盲目锁定'this'的另一个问题是你可能有几个方法在同一个锁上竞争,逻辑上不需要互斥. (5认同)
  • @eljenso - 我concu.锁定"this"可以允许外部调用者"原子化"多个方法调用,这可能是非常宝贵的; 考虑迭代同步集合; 或者PrintWriters使用私有锁的痛苦 - 曾经尝试过不间断地编写堆栈跟踪? (2认同)

Ken*_*Lai 6

只是为了强调Java中还有ReadWriteLocks,可以找到java.util.concurrent.locks.ReadWriteLock.

在我的大部分用法中,我将锁定分为"阅读"和"更新".如果您只使用synchronized关键字,则对同一方法/代码块的所有读取都将"排队".一次只能有一个线程访问该块.

在大多数情况下,如果您只是在阅读,就不必担心并发问题.当你在写作时,你担心并发更新(导致数据丢失),或者在写入(部分更新)期间读取,你必须担心.

因此,在多线程编程期间,读/写锁对我来说更有意义.


Ele*_*onk 5

您需要在可以充当互斥体的对象上进行同步。如果当前实例(this引用)合适(例如,不是单例),您可以使用它,就像在 Java 中任何对象都可以充当互斥体一样。

在其他情况下,如果这些类的实例可能都需要访问相同的资源,您可能希望在多个类之间共享互斥锁。

这在很大程度上取决于您正在工作的环境以及您正在构建的系统类型。在我见过的大多数 Java EE 应用程序中,实际上并不需要同步......