在 Java 临界区中,我应该同步什么?

发布于 2024-07-11 11:33:59 字数 303 浏览 8 评论 0原文

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

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

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

In Java, the idiomatic way to declare critical sections in the code is the following:

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Almost all blocks synchronize on this, but is there a particular reason for this? Are there other possibilities? Are there any best practices on what object to synchronize on? (such as private instances of Object?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

无戏配角 2024-07-18 11:33:59

正如前面的回答者所指出的,最佳实践是在有限范围的对象上进行同步(换句话说,选择您可以摆脱的最严格的范围,然后使用它。)特别是在 this 是一个坏主意,除非您打算允许您的类的用户获得锁定。

但是,如果您选择在 java.lang.String 上进行同步,就会出现一种特别丑陋的情况。 字符串可以(并且在实践中几乎总是)被保留。 这意味着在整个 JVM 中,每个内容相同的字符串在幕后都是相同的字符串。 这意味着,如果您在任何字符串上进行同步,另一个(完全不同的)代码段也锁定具有相同内容的字符串,实际上也会锁定您的代码。

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

As earlier answerers have noted, it is best practice to synchronize on an object of limited scope (in other words, pick the most restrictive scope you can get away with, and use that.) In particular, synchronizing on this is a bad idea, unless you intend to allow the users of your class to gain the lock.

A particularly ugly case arises, though, if you choose to synchronize on a java.lang.String. Strings can be (and in practice almost always are) interned. That means that each string of equal content - in the ENTIRE JVM - turns out to be the same string behind the scenes. That means that if you synchronize on any String, another (completely disparate) code section that also locks on a String with the same content, will actually lock your code as well.

I was once troubleshooting a deadlock in a production system and (very painfully) tracked the deadlock to two completely disparate open source packages that each synchronized on an instance of String whose contents were both "LOCK".

风尘浪孓 2024-07-18 11:33:59

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

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

First, note that the following code snippets are identical.

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}

and:

public synchronized void foo() {
    // do something thread-safe
}

do exactly the same thing. No preference for either one of them except for code readability and style.

When you do synchronize methods or blocks of code, it's important to know why you are doing such a thing, and what object exactly you are locking, and for what purpose.

Also note that there are situations in which you will want to client-side synchronize blocks of code in which the monitor you are asking for (i.e. the synchronized object) is not necessarily this, like in this example :

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}

I suggest you get more knowledge about concurrent programming, it will serve you a great deal once you know exactly what's happening behind the scenes. You should check out Concurrent Programming in Java, a great book on the subject. If you want a quick dive-in to the subject, check out Java Concurrency @ Sun

心的憧憬 2024-07-18 11:33:59

我尝试避免在 this 上进行同步,因为这将允许外部引用该对象的每个人阻止我的同步。 相反,我创建一个本地同步对象:

public class Foo {
    private final Object syncObject = new Object();
    …
}

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

I try to avoid synchronizing on this because that would allow everybody from the outside who had a reference to that object to block my synchronization. Instead, I create a local synchronization object:

public class Foo {
    private final Object syncObject = new Object();
    …
}

Now I can use that object for synchronization without fear of anybody “stealing” the lock.

面犯桃花 2024-07-18 11:33:59

只是为了强调一下,Java 中也有可用的 ReadWriteLock,如 java.util.concurrent.locks.ReadWriteLock。

在我的大多数使用中,我将锁定分为“用于阅读”和“用于更新”。 如果您仅使用同步关键字,则对同一方法/代码块的所有读取都将“排队”。 一次只有一个线程可以访问该块。

在大多数情况下,如果您只是进行读取,则永远不必担心并发问题。 当您进行写入时,您需要担心并发更新(导致数据丢失),或者写入期间的读取(部分更新),这是您必须担心的。

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

Just to highlight that there are also ReadWriteLocks available in Java, found as java.util.concurrent.locks.ReadWriteLock.

In most of my usage, I seperate my locking as 'for reading' and 'for updates'. If you simply use a synchronized keyword, all reads to the same method/code block will be 'queued'. Only one thread can access the block at one time.

In most cases, you never have to worry about concurrency issues if you are simply doing reading. It is when you are doing writing that you worry about concurrent updates (resulting in lost of data), or reading during a write (partial updates), that you have to worry about.

Therefore a read/write lock makes more sense to me during multi-threaded programming.

梦里的微风 2024-07-18 11:33:59

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

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

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

You'll want to synchronize on an object that can serve as a Mutex. If the current instance (the this reference) is suitable (not a Singleton, for instance), you may use it, as in Java any Object may serve as the Mutex.

In other occasions, you may want to share a Mutex between several classes, if instances of these classes may all need access to the same resources.

It depends a lot on the environment you're working in and the type of system you're building. In most Java EE applications I've seen, there's actually no real need for synchronization...

时光无声 2024-07-18 11:33:59

就我个人而言,我认为那些坚持认为在 this 上同步永远不会或很少正确的答案是错误的。 我认为这取决于你的API。 如果您的类是线程安全实现并且您如此记录它,那么您应该使用 this。 如果同步不是为了使类的每个实例在调用其公共方法时作为一个整体线程安全,那么您应该使用私有内部对象。 可重用的库组件通常属于前一类 - 在禁止用户将您的 API 包装在外部同步中之前,您必须仔细考虑。

在前一种情况下,使用 this 允许以原子方式调用多个方法。 一个例子是 PrintWriter,您可能希望输出多行(例如控制台/记录器的堆栈跟踪)并保证它们一起出现 - 在这种情况下,它在内部隐藏同步对象这一事实是一个真正的痛苦。 另一个这样的例子是同步集合包装器 - 您必须同步集合对象本身才能进行迭代; 由于迭代由多个方法调用组成,因此您无法在内部完全保护它。

在后一种情况下,我使用普通对象:

private Object mutex=new Object();

但是,在看到许多 JVM 转储和堆栈跟踪表明锁是“java.lang.Object() 的一个实例”之后,我不得不说,使用内部类通常可能是正如其他人所建议的那样更有帮助。

无论如何,这就是我的两块钱。

编辑:另一件事,在 this 上同步时,我更喜欢同步方法,并保持方法非常精细。 我认为它更清晰、更简洁。

Personally, I think the answers which insist that it is never or only rarely correct to sync on this are misguided. I think it depends on your API. If your class is a threadsafe implementation and you so document it, then you should use this. If the synchronization is not to make each instance of the class as a whole threadsafe in the invocation of it's public methods, then you should use a private internal object. Reusable library components often fall into the former category - you must think carefully before you disallow the user to wrap your API in external synchronization.

In the former case, using this allows multiple methods to be invoked in an atomic manner. One example is PrintWriter, where you may want to output multiple lines (say a stack trace to the console/logger) and guarantee they appear together - in this case the fact that it hides the sync object internally is a real pain. Another such example are the synchronized collection wrappers - there you must synchronize on the collection object itself in order to iterate; since iteration consists of multiple method invocations you cannot protect it totally internally.

In the latter case, I use a plain object:

private Object mutex=new Object();

However, having seen many JVM dumps and stack traces that say a lock is "an instance of java.lang.Object()" I have to say that using an inner class might often be more helpful, as others have suggested.

Anyway, that's my two bits worth.

Edit: One other thing, when synchronizing on this I prefer to sync the methods, and keep the methods very granular. I think it's clearer and more concise.

寂寞笑我太脆弱 2024-07-18 11:33:59

Java 中的同步通常涉及同一实例上的同步操作。 在 this 上进行同步是非常惯用的,因为 this 是一个共享引用,在类中的不同实例方法(或部分)之间自动可用。

使用另一个专门用于锁定的引用,例如通过声明和初始化私有字段Object lock = new Object(),是我从未需要或使用过的。 我认为只有当您需要对对象内的两个或多个不同步资源进行外部同步时,它才有用,尽管我总是尝试将这种情况重构为更简单的形式。

无论如何,隐式(同步方法)或显式 synchronized(this) 被大量使用,在 Java 库中也是如此。 这是一个很好的习语,如果适用的话,应该始终是您的首选。

Synchronization in Java often involves synchronizing operations on the same instance. Synchronizing on this then is very idiomatic since this is a shared reference that is automatically available between different instance methods (or sections of) in a class.

Using another reference specifically for locking, by declaring and initializing a private field Object lock = new Object() for example, is something I never needed or used. I think it is only useful when you need external synchronization on two or more unsynchronized resources inside an object, although I would always try to refactor such a situation into a simpler form.

Anyway, implicit (synchronized method) or explicit synchronized(this) is used a lot, also in the Java libraries. It is a good idiom and, if applicable, should always be your first choice.

轻许诺言 2024-07-18 11:33:59

同步的内容取决于可能与此方法调用发生冲突的其他线程可以同步的内容。

如果 this 是一个仅由一个线程使用的对象,并且我们正在访问在线程之间共享的可变对象,那么一个好的候选方法是同步该对象 - 在 this 上同步code> 没有意义,因为修改该共享对象的另一个线程可能甚至不知道 this,但确实知道该对象。

另一方面,如果许多线程同时调用此对象的方法(例如,如果我们处于单例中),则通过 this 进行同步是有意义的。

请注意,同步方法通常不是最佳选择,因为我们在方法运行的整个过程中都持有锁。 如果它包含耗时但线程安全的部分,以及不太耗时的线程不安全部分,则在方法上进行同步是非常错误的。

On what you synchronize depends on what other threads that might potentially get into conflict with this method call can synchronize.

If this is an object that is used by only one thread and we are accessing a mutable object which is shared between threads, a good candidate is to synchronize over that object - synchronizing on this has no point since another thread that modifies that shared object might not even know this, but does know that object.

On the other hand synchronizing over this makes sense if many threads call methods of this object at the same time, for instance if we are in a singleton.

Note that a syncronized method is often not the best option, since we hold a lock the whole time the method runs. If it contains timeconsuming but thread safe parts, and a not so time consuming thread-unsafe part, synchronizing over the method is very wrong.

梦过后 2024-07-18 11:33:59

几乎所有区块都在此同步,但这有什么特殊原因吗? 还有其他可能吗?

该声明同步整个方法。

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 问题了解更多详细信息:

同步与锁定

避免在 Java 中同步(this)?

Almost all blocks synchronize on this, but is there a particular reason for this? Are there other possibilities?

This declaration synchronizes entire method.

private synchronized void doSomething() {

This declaration synchronized a part of code block instead of entire method.

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

From oracle documentation page

making these methods synchronized has two effects:

First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.

Are there other possibilities? Are there any best practices on what object to synchronize on? (such as private instances of Object?)

There are many possibilities and alternatives to synchronization. You can make your code thread safe by using high level concurrency APIs( available since JDK 1.5 release)

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

Refer to below SE questions for more details:

Synchronization vs Lock

Avoid synchronized(this) in Java?

话少心凉 2024-07-18 11:33:59

最佳实践是创建一个仅提供锁的对象:

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/并发/locksync.html

the Best Practices is to create an object solely to provide the lock:

private final Object lock = new Object();

private void doSomething() {
  // thread-safe code
  synchronized(lock) {
    // thread-unsafe code
  }
  // thread-safe code
}

By doing this you are safe, that no calling code can ever deadlock your method by an unintentional synchronized(yourObject) line.

(Credits to @jared and @yuval-adam who explained this in more details above.)

My guess is that the popularity of using this in tutorials came from early Sun javadoc: https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

蝶…霜飞 2024-07-18 11:33:59

同步包括3个部分:原子性、可见性和排序

同步块是非常粗粒度的同步。 它按照您的预期强制执行可见性和排序。 但对于原子性来说,并没有提供太多的保护。 原子性需要程序的全局知识而不是局部知识。 (这使得多线程编程非常困难)

假设我们有一个类 Account ,它有方法 depositwithdraw。 它们都基于私有锁进行同步,如下所示:

class Account {
    private Object lock = new Object();

    void withdraw(int amount) {
        synchronized(lock) {
            // ...
        }
    }

    void deposit(int amount) {
        synchronized(lock) {
            // ...
        }
    }
}

考虑到我们需要实现一个处理转账的更高级别的类,如下所示:

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        if (fromAcc.getBalance() > amount) {
            fromAcc.setBalance(fromAcc.getBalance() - amount);
            toAcc.setBalance(toAcc.getBalance + amount);
        }
    }
}

假设我们现在有 2 个帐户,

Account john;
Account marry;

如果 Account.deposit()Account.withdraw() 仅使用内部锁锁定。 当我们有 2 个线程工作时,这会导致问题:

// Some thread
void threadA() {
    john.withdraw(500);
}

// Another thread
void threadB() {
    accountManager.transfer(john, marry, 100);
}

因为 threadAthreadB 可能同时运行。 并且线程B完成条件检查,线程A退出,线程B再次退出。 这意味着即使约翰的账户没有足够的钱,我们也可以从约翰那里提取 100 美元。 这会破坏原子性。

您可能会提出:为什么不将 withdraw()deposit() 添加到 AccountManager 中呢? 但根据这个建议,我们需要创建一个多线程安全的Map,它将不同的帐户映射到它们的锁。 我们需要在执行后删除锁(否则会泄漏内存)。 我们还需要确保没有其他人直接访问Account.withdraw()。 这将引入许多微妙的错误。

正确且最惯用的方法是公开帐户中的锁。 并让AccountManager使用锁。 但在这种情况下,为什么不直接使用对象本身呢?

class Account {
    synchronized void withdraw(int amount) {
        // ...
    }

    synchronized void deposit(int amount) {
        // ...
    }
}

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        // Ensure locking order to prevent deadlock
        Account firstLock = fromAcc.hashCode() < toAcc.hashCode() ? fromAcc : toAcc;
        Account secondLock = fromAcc.hashCode() < toAcc.hashCode() ? toAcc : fromAcc;

        synchronized(firstLock) {
            synchronized(secondLock) {
                if (fromAcc.getBalance() > amount) {
                    fromAcc.setBalance(fromAcc.getBalance() - amount);
                    toAcc.setBalance(toAcc.getBalance + amount);
                }
            }
        }
    }
}

简而言之,私有锁不适用于稍微复杂的多线程程序。

Synchronization includes 3 parts: Atomicity, Visibility and Ordering

Synchronized block is very coarse level of synchronization. It enforces visibility and ordering just as what you expected. But for atomicity, it does not provide much protection. Atomicity requires global knowledge of the program rather than local knowledge. (And that makes multi-threading programming very hard)

Let's say we have a class Account having method deposit and withdraw. They are both synchronized based on a private lock like this:

class Account {
    private Object lock = new Object();

    void withdraw(int amount) {
        synchronized(lock) {
            // ...
        }
    }

    void deposit(int amount) {
        synchronized(lock) {
            // ...
        }
    }
}

Considering we need to implement a higher-level class which handles transfer, like this:

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        if (fromAcc.getBalance() > amount) {
            fromAcc.setBalance(fromAcc.getBalance() - amount);
            toAcc.setBalance(toAcc.getBalance + amount);
        }
    }
}

Assuming we have 2 accounts now,

Account john;
Account marry;

If the Account.deposit() and Account.withdraw() are locked with internal lock only. That will cause problem when we have 2 threads working:

// Some thread
void threadA() {
    john.withdraw(500);
}

// Another thread
void threadB() {
    accountManager.transfer(john, marry, 100);
}

Because it is possible for both threadA and threadB run at the same time. And thread B finishes the conditional check, thread A withdraws, and thread B withdraws again. This means we can withdraw $100 from John even if his account has no enough money. This will break atomicity.

You may propose that: why not adding withdraw() and deposit() to AccountManager then? But under this proposal, we need to create a multi-thread safe Map which maps from different accounts to their locks. We need to delete the lock after execution (otherwise will leak memory). And we also need to ensure no other one accesses the Account.withdraw() directly. This will introduce a lots of subtle bugs.

The correct and most idiomatic way is to expose the lock in the Account. And let the AccountManager to use the lock. But in this case, why not just use the object itself then?

class Account {
    synchronized void withdraw(int amount) {
        // ...
    }

    synchronized void deposit(int amount) {
        // ...
    }
}

class AccountManager {
    void transfer(Account fromAcc, Account toAcc, int amount) {
        // Ensure locking order to prevent deadlock
        Account firstLock = fromAcc.hashCode() < toAcc.hashCode() ? fromAcc : toAcc;
        Account secondLock = fromAcc.hashCode() < toAcc.hashCode() ? toAcc : fromAcc;

        synchronized(firstLock) {
            synchronized(secondLock) {
                if (fromAcc.getBalance() > amount) {
                    fromAcc.setBalance(fromAcc.getBalance() - amount);
                    toAcc.setBalance(toAcc.getBalance + amount);
                }
            }
        }
    }
}

To conclude in simple English, private lock does not work for slightly more complicated multi-threaded program.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文