同步值,而不是对象

我想在Java中做这样的事情

public void giveMoney(String userId, int money) { synchronized (userId) { Profile p = fetchProfileFromDB(userId); p.setMoney(p.getMoney() + userId); saveProfileToDB(p); } } 

但是,当然,对字符串进行同步是不正确的。 做这样的事情的正确方法是什么?

如果用户ID集合有限,则可以在String的实习版本上进行同步。

如果您需要对实习进行更多控制,可以使用String.intern() (有一些缺点)或像Guava Interners这样的东西。

原则上,您可以在Java中的任何对象上进行同步。 在String对象上同步它本身并不“不正确”; 这取决于你究竟在做什么。

但是如果userId是方法中的局部变量,那么这不会起作用。 执行该方法的每个线程都有自己的变量副本(可能是指每个线程的不同String对象); 只有当多个线程在同一个对象上同步时,才能在线程之间进行同步。

您必须在对象的成员变量上创建正在同步的对象,该对象包含您具有synchronized块的方法。 如果多个线程然后在同一个对象上调用该方法,那么您将实现互斥。

 class Something { private Object lock = new Object(); public void someMethod() { synchronized (lock) { // ... } } } 

您还可以使用java.util.concurrent.locks包中的显式锁,如果需要,可以为您提供更多控制:

 class Something { private Lock lock = new ReentrantLock(); public void someMethod() { lock.lock(); try { // ... } finally { lock.unlock(); } } } 

特别是如果你想要一个写入的独占锁,但你不希望线程在阅读时必须等待对方,你可能想要使用ReadWriteLock

我想有几个选择。

最简单的是,您可以将userId映射到线程安全映射中的锁定对象。 其他人提到了实习生,但我认为这不是一个可行的选择。

但是,更常见的选项是在p (配置文件)上进行同步。 如果getProfile()是线程安全的,那么这是合适的,并且我怀疑它可能是它的名字。

从理论上讲,由于实体对象可以进行GC编辑,因此可以在不同时间对不同对象(具有相同值)进行同步。 相互排他性仍然有保证,因为不可能同时在不同的对象上同步。

但是,如果我们在不同的对象上进行同步,则发生之前的关系是有疑问的。 我们必须检查实现以找出答案。 由于它涉及GC,Java内存模型没有解决,因此推理可能非常困难。

这是一个理论上的反对意见; 实际上我认为这不会引起任何问题。

不过,对于您的问题,可以有简单,直接和理论上正确的解决方案。 例如基于简单Java名称的锁?

您可以使用代理对象作为字符串。

 Object userIdMutex = new Object(); synchronized (userIdMutex) { Profile p = getProfile(userId); p.setMoney(p.getMoney() + p); saveProfile(p); } 

无论何时访问userId使用此互斥锁。

根据您的示例,我假设您要获取对配置文件类的锁定,更改它,然后释放锁定。 在我看来,同步并不是您所需要的。 您需要一个管理这些记录的类,并允许您在需要对其进行更改时锁定和解锁记录,即源代码控制样式。

看看这个: 锁类Java 5

那这个呢:

 String userId = ...; Object userIdLock = new Object(); synchronized (userIdLock) { Profile p = getProfile(userId); p.setMoney(p.getMoney() + p); saveProfile(p); } 

它很简单,最重要的是显而易见