我想在Java中做这样的事情
public void giveMoney(String userId, int money) {
synchronized (userId) {
Profile p = fetchProfileFromDB(userId);
p.setMoney(p.getMoney() + userId);
saveProfileToDB(p);
}
}
但是,当然,对字符串进行同步是不正确的。做这样的事情的正确方法是什么?
答案 0 :(得分:9)
如果用户ID集合有限,您可以在String
的实习版本上进行同步。
如果您需要更多地控制实习,可以使用String.intern()
(有一些缺点)或类似Guava Interners
。
答案 1 :(得分:5)
原则上,您可以在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
。
答案 2 :(得分:4)
我想有几个选择。
最简单的是,您可以将userId
映射到线程安全映射中的锁定对象。其他人提到了实习生,但我认为这不是一个可行的选择。
但是,更常见的选项是在p
(配置文件)上进行同步。如果getProfile()
是线程安全的,那么这是合适的,并且我怀疑它可能是它的名字。
答案 3 :(得分:3)
从理论上讲,由于实体对象可以进行GC编辑,因此可以在不同时间对不同对象(具有相同值)进行同步。相互排他性仍然有保证,因为无法在相同的时间同步不同的对象。
但是,如果我们在不同的对象上进行同步,那么之前发生的关系是有疑问的。我们必须检查实现以找出答案。由于它涉及GC,Java内存模型没有解决,因此推理可能非常困难。
这是一个理论上的反对意见;实际上我认为这不会导致任何问题。
但是,对于您的问题,可以有简单,直接和理论上正确的解决方案。例如Simple Java name based locks?
答案 4 :(得分:1)
您可以使用代理对象作为字符串。
Object userIdMutex = new Object();
synchronized (userIdMutex) {
Profile p = getProfile(userId);
p.setMoney(p.getMoney() + p);
saveProfile(p);
}
每次访问userId
时都使用此互斥锁。
答案 5 :(得分:0)
根据您的示例,我假设您要获取对配置文件类的锁定,更改它,然后释放锁定。在我看来,同步并不是您所需要的。您需要一个管理这些记录的类,并允许您在需要对其进行更改时锁定和解锁记录,即源代码控制样式。
检查出来:Lock class Java 5
答案 6 :(得分:-3)
这个怎么样:
String userId = ...;
Object userIdLock = new Object();
synchronized (userIdLock) {
Profile p = getProfile(userId);
p.setMoney(p.getMoney() + p);
saveProfile(p);
}
这很简单,最重要的是显而易见。