同步值,而不是对象

时间:2011-09-26 13:29:33

标签: java thread-safety synchronized

我想在Java中做这样的事情

  public void giveMoney(String userId, int money) {
    synchronized (userId) {

        Profile p = fetchProfileFromDB(userId);
        p.setMoney(p.getMoney() + userId);
        saveProfileToDB(p);

    }
   }

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

7 个答案:

答案 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);
}

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