同步值,而不是对象
我想在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); }
它很简单,最重要的是显而易见 。