【问题标题】:Synchronize on two String objects在两个 String 对象上同步
【发布时间】:2022-01-16 23:08:45
【问题描述】:

我只找到了一个 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 的情况下做到这一点?因为这张地图可能会变得非常大,而且对性能不利

【问题讨论】:

标签: java string concurrency synchronization


【解决方案1】:

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

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

【讨论】:

  • 但这是方法参数上的同步,被认为不适合多线程
  • 你是对的!只要您拥有相同的对象(相同的引用),它就可以工作。我忘记了你正在处理的字符串(可能)没有相同的参考......我改变了我的答案,所以它现在肯定可以工作了。
  • 这段代码还是有问题的。当源和目标相同时,没有合理的理由使用不同的锁定对象 (syncOnEquals)。这样,它可以与从该账户或向该账户的其他转账重叠。修复后,您可以将Hashtable 替换为ConcurrentHashMap;它不需要锁定整个哈希映射,但仍然可以做正确的事情。
  • 没有 ConcurrentHashMap 有没有办法做到这一点?因为这张地图可能会变得非常大,而且对性能不利
【解决方案2】:

必须明确的是,对于 transfer (from, to) all (from, ), (, from), (*, to), ( to, *) 传输可能需要被保护。

做到这一点的唯一方法是同步 from 和同步 on to。当存在 transfer(to、from)时,这可能会导致 dead lock。因为它可以从和到排序,以便同步 AAA 在同步 BBB 之前。

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

现在在同步对象上:这必须是一个唯一的 Object 实例。您可以使用帐户 ID 字符串,如放入 Map (Set)。因此,您有一个唯一的对象作为地图中的键。当然,地图操作也会受到并发性的影响,请使用:Collections.synchronizedMap。您需要更新它,添加/删除帐户 ID。

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

【讨论】:

    猜你喜欢
    • 2010-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多