在 Java 中避免同步(这个)?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/442564/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 14:40:49  来源:igfitidea点击:

Avoid synchronized(this) in Java?

javamultithreadingsynchronizationsynchronized

提问by eljenso

Whenever a question pops up on SO about Java synchronization, some people are very eager to point out that synchronized(this)should be avoided. Instead, they claim, a lock on a private reference is to be preferred.

每当出现关于 Java 同步的 SO 问题时,有些人非常渴望指出synchronized(this)应该避免的问题。相反,他们声称,最好锁定私有引用。

Some of the given reasons are:

给出的一些原因是:

Other people, including me, argue that synchronized(this)is an idiom that is used a lot (also in Java libraries), is safe and well understood. It should not be avoided because you have a bug and you don't have a clue of what is going on in your multithreaded program. In other words: if it is applicable, then use it.

包括我在内的其他人认为这synchronized(this)是一个经常使用的习语(也在 Java 库中),是安全且易于理解的。不应该避免它,因为您有一个错误并且您不知道多线程程序中发生了什么。换句话说:如果适用,则使用它。

I am interested in seeing some real-world examples (no foobar stuff) where avoiding a lock on thisis preferable when synchronized(this)would also do the job.

我有兴趣看到一些现实世界的例子(没有 foobar 的东西),在这些例子中,避免锁定this是最好的,synchronized(this)同时也可以完成这项工作。

Therefore: should you always avoid synchronized(this)and replace it with a lock on a private reference?

因此:您是否应该始终避免synchronized(this)并用私有引用上的锁替换它?



Some further info (updated as answers are given):

一些进一步的信息(在给出答案时更新):

  • we are talking about instance synchronization
  • both implicit (synchronizedmethods) 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方法)和显式形式synchronized(this)都被考虑
  • 如果你引用 Bloch 或其他权威人士的话说,不要遗漏你不喜欢的部分(例如 Effective Java,线程安全项目:通常是实例本身的锁,但也有例外。)
  • 如果您需要除synchronized(this)提供之外的锁定粒度,synchronized(this)则不适用,所以这不是问题

采纳答案by Darron

I'll cover each point separately.

我将分别介绍每一点。

  1. Some evil code may steal your lock (very popular this one, also has an "accidentally" variant)

    I'm more worried about accidentally. What it amounts to is that this use of thisis part of your class' exposed interface, and should be documented. Sometimes the ability of other code to use your lock is desired. This is true of things like Collections.synchronizedMap(see the javadoc).

  2. All synchronized methods within the same class use the exact same lock, which reduces throughput

    This is overly simplistic thinking; just getting rid of synchronized(this)won't solve the problem. Proper synchronization for throughput will take more thought.

  3. You are (unnecessarily) exposing too much information

    This is a variant of #1. Use of synchronized(this)is part of your interface. If you don't want/need this exposed, don't do it.

  1. 一些邪恶的代码可能会窃取你的锁(这个很流行,也有一个“意外”变体)

    我更担心不小心。它的意思是,这种使用this是您的类的公开接口的一部分,应该记录在案。有时需要其他代码使用您的锁的能力。这适用于Collections.synchronizedMap(参见 javadoc)之类的事情。

  2. 同一类中的所有同步方法都使用完全相同的锁,这会降低吞吐量

    这是过于简单化的想法;仅仅摆脱synchronized(this)并不能解决问题。吞吐量的正确同步需要更多考虑。

  3. 你(不必要地)暴露了太多信息

    这是#1 的变体。使用synchronized(this)是界面的一部分。如果您不想/不需要暴露这个,请不要这样做。

回答by cletus

Well, firstly it should be pointed out that:

那么首先要指出的是:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

is semantically equivalent to:

在语义上等同于:

public synchronized void blah() {
  // do stuff
}

which is one reason not to use synchronized(this). You might argue that you can do stuff around the synchronized(this)block. The usual reason is to try and avoid having to do the synchronized check at all, which leads to all sorts of concurrency problems, specifically the double checked-locking problem, which just goes to show how difficult it can be to make a relatively simple check threadsafe.

这是不使用synchronized(this). 你可能会争辩说你可以在synchronized(this)街区周围做些事情。通常的原因是尽量避免必须进行同步检查,这会导致各种并发问题,特别是双重检查锁定问题,这只是表明进行相对简单的检查是多么困难线程安全。

A private lock is a defensive mechanism, which is never a bad idea.

私有锁是一种防御机制,这绝不是一个坏主意。

Also, as you alluded to, private locks can control granularity. One set of operations on an object might be totally unrelated to another but synchronized(this)will mutually exclude access to all of them.

此外,正如您所提到的,私有锁可以控制粒度。对一个对象的一组操作可能与另一组完全无关,但synchronized(this)会相互排斥对所有这些操作的访问。

synchronized(this)just really doesn't give you anything.

synchronized(this)只是真的没有给你任何东西。

回答by Yoni Roit

I think points one (somebody else using your lock) and two (all methods using the same lock needlessly) can happen in any fairly large application. Especially when there's no good communication between developers.

我认为第一点(其他人使用你的锁)和第二点(所有方法都不必要地使用同一个锁)可能发生在任何相当大的应用程序中。特别是当开发人员之间没有良好的沟通时。

It's not cast in stone, it's mostly an issue of good practice and preventing errors.

它不是一成不变的,它主要是一个良好实践和防止错误的问题。

回答by Andrzej Doyle

No, you shouldn't always. However, I tend to avoid it when there are multiple concerns on a particular object that only need to be threadsafe in respect to themselves. For example, you might have a mutable data object that has "label" and "parent" fields; these need to be threadsafe, but changing one need not block the other from being written/read. (In practice I would avoid this by declaring the fields volatile and/or using java.util.concurrent's AtomicFoo wrappers).

不,你不应该总是。但是,当对特定对象有多个关注点时,我倾向于避免它,这些对象只需要相对于它们自己是线程安全的。例如,您可能有一个具有“标签”和“父”字段的可变数据对象;这些需要是线程安全的,但更改一个不需要阻止另一个被写入/读取。(实际上,我会通过声明字段 volatile 和/或使用 java.util.concurrent 的 AtomicFoo 包装器来避免这种情况)。

Synchronization in general is a bit clumsy, as it slaps a big lock down rather than thinking exactly how threads might be allowed to work around each other. Using synchronized(this)is even clumsier and anti-social, as it's saying "no-one may change anythingon this class while I hold the lock". How often do you actually need to do that?

一般来说,同步有点笨拙,因为它关闭了一个大锁,而不是确切地考虑如何允许线程相互工作。使用synchronized(this)甚至更笨拙和反社会,因为它说“当我拿着锁时,没有人可以改变这个类的任何东西”。您实际上需要多久这样做一次?

I would much rather have more granular locks; even if you do want to stop everything from changing (perhaps you're serialising the object), you can just acquire all of the locks to achieve the same thing, plus it's more explicit that way. When you use synchronized(this), it's not clear exactly why you're synchronizing, or what the side effects might be. If you use synchronized(labelMonitor), or even better labelLock.getWriteLock().lock(), it's clear what you are doing and what the effects of your critical section are limited to.

我宁愿拥有更细粒度的锁;即使您确实想阻止一切更改(也许您正在序列化对象),您也可以获取所有锁来实现相同的目的,而且这种方式更加明确。当您使用 时synchronized(this),不清楚为什么要同步,或者可能有什么副作用。如果您使用synchronized(labelMonitor),或者甚至更好labelLock.getWriteLock().lock(),那么您正在做什么以及您的临界区的影响仅限于什么就很清楚了。

回答by tcurdt

Short answer: You have to understand the difference and make choice depending on the code.

简短回答:您必须了解差异并根据代码做出选择。

Long answer: In general I would rather try to avoid synchronize(this)to reduce contention but private locks add complexity you have to be aware of. So use the right synchronization for the right job. If you are not so experienced with multi-threaded programming I would rather stick to instance locking and read up on this topic. (That said: just using synchronize(this)does not automatically make your class fully thread-safe.) This is a not an easy topic but once you get used to it, the answer whether to use synchronize(this)or not comes naturally.

长答案:一般来说,我宁愿尝试避免同步(this)以减少争用,但私有锁会增加您必须注意的复杂性。因此,为正确的工作使用正确的同步。如果您对多线程编程没有那么丰富的经验,我宁愿坚持使用实例锁定并阅读有关此主题的内容。(也就是说:仅使用同步(this)不会自动使您的类完全线程安全。)这不是一个容易的话题,但是一旦您习惯了它,是否使用同步(this)的答案就自然而然地出现了.

回答by Harald Schilly

It depends on the task you want to do, but I wouldn't use it. Also, check if the thread-save-ness you want to accompish couldn't be done by synchronize(this) in the first place? There are also some nice locks in the APIthat might help you :)

这取决于你想做的任务,但我不会使用它。另外,首先检查您想要实现的线程保存是否不能通过同步(this)来完成?API中还有一些不错的可能对您有所帮助:)

回答by Andreas Bakurov

While you are using synchronized(this) you are using the class instance as a lock itself. This means that while lock is acquired by thread 1, the thread 2should wait.

当您使用 synchronized(this) 时,您将类实例用作锁本身。这意味着当线程 1获取锁时,线程 2应该等待。

Suppose the following code:

假设以下代码:

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}


public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

Method 1 modifying the variable aand method 2 modifying the variable b, the concurrent modification of the same variable by two threads should be avoided and it is. BUT while thread1modifying aand thread2modifying bit can be performed without any race condition.

方法一修改变量a和方法二修改变量b,应该避免两个线程同时修改同一个变量,确实如此。但是,当线程 1修改a线程 2修改b 时,它可以在没有任何竞争条件的情况下执行。

Unfortunately, the above code will not allow this since we are using the same reference for a lock; This means that threads even if they are not in a race condition should wait and obviously the code sacrifices concurrency of the program.

不幸的是,上面的代码不允许这样做,因为我们对锁使用相同的引用;这意味着线程即使不在竞争条件下也应该等待,显然代码牺牲了程序的并发性。

The solution is to use 2different locks for twodifferent variables:

解决方案是对两个不同的变量使用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 ++;      
        }
        // ................
    }

}

The above example uses more fine grained locks (2 locks instead one (lockAand lockBfor variables aand brespectively) and as a result allows better concurrency, on the other hand it became more complex than the first example ...

上面的例子中的用途更细粒度的锁(2把锁而不是一个(洛卡LOCKB变量一个b分别地),其结果允许更好的并发性,在另一方面,它变得比第一示例更复杂...

回答by Olivier

While I agree about not adhering blindly to dogmatic rules, does the "lock stealing" scenario seem so eccentric to you? A thread could indeed acquire the lock on your object "externally"(synchronized(theObject) {...}), blocking other threads waiting on synchronized instance methods.

虽然我同意不盲目遵守教条规则,但“窃取锁”的情况对您来说是否如此古怪?线程确实可以“从外部”( synchronized(theObject) {...})获取对象上的锁,从而阻塞等待同步实例方法的其他线程。

If you don't believe in malicious code, consider that this code could come from third parties (for instance if you develop some sort of application server).

如果您不相信恶意代码,请考虑此代码可能来自第三方(例如,如果您开发某种应用程序服务器)。

The "accidental" version seems less likely, but as they say, "make something idiot-proof and someone will invent a better idiot".

“偶然”的版本似乎不太可能,但正如他们所说,“制作一些防白痴的东西,有人会发明一个更好的白痴”。

So I agree with the it-depends-on-what-the-class-does school of thought.

所以我同意它取决于班级做什么的思想流派。



Edit following eljenso's first 3 comments:

编辑以下 eljenso 的前 3 条评论:

I've never experienced the lock stealing problem but here is an imaginary scenario:

我从未遇到过锁窃取问题,但这是一个想象的场景:

Let's say your system is a servlet container, and the object we're considering is the ServletContextimplementation. Its getAttributemethod must be thread-safe, as context attributes are shared data; so you declare it as synchronized. Let's also imagine that you provide a public hosting service based on your container implementation.

假设您的系统是一个 servlet 容器,我们正在考虑的对象是ServletContext实现。它的getAttribute方法必须是线程安全的,因为上下文属性是共享数据;所以你把它声明为synchronized. 我们还假设您提供基于容器实现的公共托管服务。

I'm your customer and deploy my "good" servlet on your site. It happens that my code contains a call to getAttribute.

我是您的客户,并在您的站点上部署了我的“好”servlet。碰巧我的代码包含对getAttribute.

A hacker, disguised as another customer, deploys his malicious servlet on your site. It contains the following code in the initmethod:

伪装成另一个客户的黑客在您的站点上部署了他的恶意 servlet。它在init方法中包含以下代码:

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

Assuming we share the same servlet context (allowed by the spec as long as the two servlets are on the same virtual host), my call on getAttributeis locked forever. The hacker has achieved a DoS on my servlet.

假设我们共享相同的 servlet 上下文(规范允许,只要两个 servlet 位于同一个虚拟主机上),我的调用getAttribute就会被永远锁定。黑客在我的 servlet 上实现了 DoS。

This attack is not possible if getAttributeis synchronized on a private lock, because 3rd-party code cannot acquire this lock.

如果getAttribute在私有锁上同步,则无法进行这种攻击,因为 3rd 方代码无法获取此锁。

I admit that the example is contrived and an oversimplistic view of how a servlet container works, but IMHO it proves the point.

我承认这个例子是人为的,对 servlet 容器的工作方式的看法过于简单,但恕我直言,它证明了这一点。

So I would make my design choice based on security consideration: will I have complete control over the code that has access to the instances? What would be the consequence of a thread's holding a lock on an instance indefinitely?

所以我会基于安全考虑做出我的设计选择:我是否可以完全控制可以访问实例的代码?一个线程无限期地持有一个实例的锁会产生什么后果?

回答by serg10

There seems a different consensus in the C# and Java camps on this.The majority of Java code I have seen uses:

C# 和 Java 阵营对此似乎有不同的共识。我见过的大多数 Java 代码都使用:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

whereas the majority of C# code opts for the arguably safer:

而大多数 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
}

The C# idiom is certainly safer. As mentioned previously, no malicious / accidental access to the lock can be made from outside the instance. Java code has this risk too, but it seems that the Java community has gravitated over time to the slightly less safe, but slightly more terse version.

C# 习惯用法当然更安全。如前所述,不能从实例外部对锁进行恶意/意外访问。Java 代码也有这种风险,但随着时间的推移,Java 社区似乎已经被吸引到稍微不那么安全但稍微更简洁的版本。

That's not meant as a dig against Java, just a reflection of my experience working on both languages.

这并不意味着对 Java 的挖掘,只是反映了我在这两种语言上工作的经验。

回答by jamesh

The java.util.concurrentpackage has vastly reduced the complexity of my thread safe code. I only have anecdotal evidence to go on, but most work I have seen with synchronized(x)appears to be re-implementing a Lock, Semaphore, or Latch, but using the lower-level monitors.

java.util.concurrent包大大降低了我的线程安全代码的复杂性。我只有轶事证据可以继续,但我见过的大多数工作synchronized(x)似乎都是重新实现锁、信号量或锁存器,但使用的是较低级别的监视器。

With this in mind, synchronizing using any of these mechanisms is analogous to synchronizing on an internal object, rather than leaking a lock. This is beneficial in that you have absolute certainty that you control the entry into the monitor by two or more threads.

考虑到这一点,使用这些机制中的任何一种进行同步类似于在内部对象上进行同步,而不是泄漏锁。这是有益的,因为您绝对确定您可以通过两个或多个线程控制进入监视器。