是否可以使用两个嵌套的同步块来锁定数组的两个单元格以进行原子操作?

Can one be using two nested synchronized blocks in order to lock two cells of an array for atomic operation?

我有一个 class Repository,它分配表示为 cells 数组字段值的资源。该方法将资源从一个单元移动到另一个单元。

我需要确保当我们将资源从一个单元格移动到另一个单元格时,不能对相关单元格执行其他移动操作。也就是说,如果一个单元格(原始单元格或目标单元格)参与 move 操作,我们必须等到当前操作完成后才能对这些单元格执行另一个 move 操作。对不同的单元格对并行执行move应该没有限制,例如move(cells[1], cells[2], 5)move(cells[4], cells[7], 9)可以并行执行。

我想使用两个嵌套的 synchronized 块来保护源单元格和目标单元格。我认为我们不需要使用 wait/notifyAll,因为无论如何我们都在使用 synchronized

我走在正确的轨道上吗?

这是代码(moveOriginal是原始方法,moveSynchronized是被保护的方法:

public class Repository {
    private Integer[] cells;

    public Repository(int size, int initialValue) {
        cells = new Integer[size];
        for(int i = 0; i < size; i++) {
            cells[i] = initialValue;
        }
    }

    public void moveOriginal(int from, int to, int amount) {
        if(cells[from] >= amount) {
            cells[from] = cells[from] - amount;
            cells[to] = cells[to] + amount;
        }
    }

    public void moveSynchronized(int from, int to, int amount) {
        synchronized(cells[from]) {
            synchronized (cells[to]) {
                if(cells[from] >= amount) {
                    cells[from] = cells[from] - amount;
                    cells[to] = cells[to] + amount;
                }
            }
        }
    }
}

这种方法存在多个问题。

  1. synchronized 锁定了 cell[x]Integer 的值,所以一旦你这样做

    cells[from] = cells[from] - amount;
    

    cells[from] 上同步将锁定不同的对象。

  2. Integer 实习表示小值的对象。像那样锁定共享对象可能会将您锁定在一个完全错误的单元格之外。

  3. 共享 Integer 对象的可用性超出了您的线程范围。另一个线程可以获得您同步的共享整数,并将您锁定在 cells 之外,而无法访问您的 class.
  4. 的私有数据结构

如果你想cell-level锁定,创建一个专门用于锁定相应单元格的对象数组;不要锁定单元格值。

不,你不能那样做。为此,您需要锁定数组本身,以防止并行操作。

Integer 是不可变的,这意味着 cells[to] = cells[to] + amount 将另一个对象放在那里,而不是修改其中的 Integer。当 synchronized(cells[to]) 在不同的时间引用不同的对象时,这将导致问题。

解决此问题的最简单方法是使用 Objects 初始化 Object[] lockArray = new Object[size]; 并同步它们。它们不会在您的业务逻辑中发生变化。

在任何情况下,您都需要嵌套 synchronized 作用域。您还需要做的是define an order,例如总是首先同步较小的值。否则,当多个线程尝试同时执行类似 move(1, 2); move(2, 1); 的操作时,您会遇到死锁。

public void moveSynchronized(int from, int to, int amount) {
    if(from == to || from < 0 || to < 0 || from > cells.length || to.cells.length)
        throw new IllegalArgumentException("Bad values ! " + from + ", " + to);

    // Lock1 is always smaller and locked first
    int lock1 = from < to ? from : to;
    int lock2 = from < to ? to : from;

    synchronized(locks[lock1]) {
        synchronized (locks[lock2]) {
            if(cells[from] >= amount) {
                cells[from] = cells[from] - amount;
                cells[to] = cells[to] + amount;
            }
        }
    }
}