在两个 String 对象上同步

Synchronize on two String objects

我只在一个 String 对象上找到了同步的答案,在两个上找不到。

这不是真正的任务,而是作业。我有 SomeLibrary 可以将钱从一个帐户转移到另一个帐户。我无法访问 Account 对象来锁定它。我只能使用 SomeLibrary.transfer(String from, String to),这不是线程安全的。我有将帐户 ID 作为字符串的方法。我需要在没有死锁的情况下锁定这两个字符串。

到目前为止我所做的是:

  1. 使用 .intern 方法创建了新字符串 (String fr = from.intern())。但这是不好的做法,我不允许使用这种方法。但它奏效了。

  2. 从旧字符串创建新字符串 (String fr = new String(from))。这也有效(我没有死锁)但我怀疑这个解决方案。

是否有其他方法可以锁定两个字符串?

我尝试使用 ConcurrentHashMap 并将字符串放在那里,但没有用。

可能有一种方法可以将字符串放入某些对象中,但是应该在哪里创建这些对象?我可以在 transfer() 中创建它们,但是局部变量上的同步也不是好的做法。

我的方法是:

public void transfer(String from, String to, int amount) {
        String fr = new String(from);
        String too = new String(to);

        int fromHash = System.identityHashCode(fr);
        int toHash = System.identityHashCode(too);

        if (fromHash < toHash) {
            synchronizedTransfer(from, to, amount, fr, too);
        } else if (fromHash > toHash) {
            synchronizedTransfer(to, from, amount, too, fr);
        } else {
            synchronized (tieLock) {
                synchronizedTransfer(from, to, amount, fr, too);
            }
        }
    }

private void synchronizedTransfer(String from, String to, int amount, String fr, String too) {
        synchronized (fr) {
            synchronized (too) {
                SomeLibrary.transfer(from, to);
            }
        }
    }

编辑:

有没有不用 ConcurrentHashMap 的方法?因为这张地图可能会变得很大而且不利于性能

您可以使用嵌套的同步块和存储专用同步对象的数据结构来同步这两个对象。 为了防止出现任何死锁,我会按字典顺序比较这两个字符串:

private static final ConcurrentHashMap<String, Object> syncMap = new ConcurrentHashMap<>();

    public void transfer(String from, String to, int amount) {
        Object syncFrom = syncMap.computeIfAbsent(from, s -> new Object());
        Object syncTo = syncMap.computeIfAbsent(to, s -> new Object());

        int comparingResult = from.compareTo(to);

        if (comparingResult > 0) {
            synchronized (syncFrom) {
                synchronized (syncTo) {
                    SomeLibrary.transfer(from, to);
                }
            }
        } else if (comparingResult < 0) {
            synchronized (syncTo) {
                synchronized (syncFrom) {
                    SomeLibrary.transfer(from, to);
                }
            }
        } else {
                synchronized (syncFrom) { // syncFrom == syncTo
                    SomeLibrary.transfer(from, to);
                }
        }
    }

如果您不熟悉 lambda 表达式:

Object syncFrom = syncMap.computeIfAbsent(from, s -> new Object());

相当于

Object syncFrom;
synchronized (syncMap) {
    syncFrom = syncMap.get(from);
    if (syncFrom == null) {
        syncFrom = new Object();
        syncMap.put(from, syncFrom);
    }
}

必须清楚的是,对于一个transfer (from, to) all (from, ), (, from) , (*, to), (to, *) 转账可能需要看守。

做到这一点的唯一方法是同步 from 和同步 on to。当存在 transfer (to, from) 时,这可能会导致 死锁 。为此,可以从和到顺序排序,以便同步 AAA 在同步 BBB 之前出现。

另一种方法是原子 deposit (from, -amount), deposit (to, amount) 并在可以回滚的事务中完成所有操作。

现在关于同步对象:这必须是唯一的对象实例。您可以使用帐户 ID 字符串,如 Map (Set) 中那样。因此,您有一个独特的对象作为地图中的键。当然 map 操作也受到并发性的影响,使用:Collections.synchronizedMap。您需要更新它,add/remove 个帐户 ID。

由于事务是一个单独的主题,因此按规范顺序嵌套两个同步。