简体   繁体   English

如何安全地深度复制不规则2D数组线程

[英]How do I deep copy an irregular 2D array threadsafely

I have stumbled upon an annoyance as I was writing a Java class; 在编写Java类时,我偶然发现了一个烦恼。 I couldn't figure out how to make copying a 2 dimensional array thread safe. 我不知道如何使复制二维数组线程安全。 This is the simple version of the class: 这是该类的简单版本:

public class Frame {
  private boolean[][] content;

  public Frame(boolean [][] content) {
    boolean[][] threadSafeCopy = deepCopy(content);
    if (!(threadSafeCopy != null)) {
      throw new NullPointerException("content must not be null");
    }
    if (!(threadSafeCopy.length > 0)) {
      throw new IllegalArgumentException("content.length must be greater than 0");
    }
    if (!(threadSafeCopy[0] != null)) {
      throw new IllegalArgumentException("content[0] must not be null");
    }
    if (!(threadSafeCopy[0].length > 0)) {
      throw new IllegalArgumentException("content[0].length must be greater than 0");
    }
    for (int i = 1, count = threadSafeCopy.length; i < count; ++i) {
      if (!(threadSafeCopy[i].length == threadSafeCopy[0].length)) {
        throw new IllegalArgumentException
            (   "content[" + i + "].length [" + threadSafeCopy[i].length
              + "] must be equal to content[0].length [" + threadSafeCopy[0].length + "]"
            );
      }
    }
    this.content = threadSafeCopy;
  }

  private boolean[][] deepCopy(boolean[][] content) {
    boolean[][] result = null;
    if (content != null) {
      synchronized(content) { //do our best to make this as multi-threaded friendly as possible
        result = new boolean[content.length][];
        for (int i = 0, count = content.length; i < count; ++ i) {
          if (content[i] != null)
          {
            synchronized(content[i]) {
              result[i] = content[i].clone();
            }
          }
        }
      }
    }
    return result;
  }

  public boolean[][] getContent() {
    boolean[][] result = new boolean[this.content.length][]; //defensive copy
    for (int i = 0, count = result.length; i < count; ++i) {
      result[i] = this.content[i].clone(); //defensive copy
    }
    return result;
  }
}

However, the above implementation for the method private boolean[][] deepCopy(boolean[][] content) isn't actually threadsafe. 但是,上述方法private boolean[][] deepCopy(boolean[][] content)的实现实际上不是线程安全的。 It's possible that the array is being actively modified by another thread while this method is attempting the copy. 在此方法尝试复制时,该数组可能正在被另一个线程主动修改。 Granted, I have guarded against the most abusive case, using synchronized on the base array. 的确,我在基本数组上使用了synchronized ,以防出现最滥用情况。 However, that doesn't cause the set of 2nd dimension array instances to be locked. 但是,这不会导致第二维数组实例集被锁定。 And it's possible for them to be modified during the copy. 并且有可能在复制期间对其进行修改。

Is there is some way to collect the Object lock for each of the base array ( content ) and the sub-arrays ( content[0] , content[1] , ..., content[content.length - 1] ) such that I can call something like synchronized(objectsToLockSimultaneouslyList) and it lock all the objects simultaneously and in the list's order. 是否有某种方法可以为基本数组( content )和子数组( content[0]content[1] ,..., content[content.length - 1] )收集对象锁,从而我可以调用“ synchronized(objectsToLockSimultaneouslyList)类的东西,它可以同时按照列表的顺序锁定所有对象。 If so, I can thread safely copy the contents of the array. 如果是这样,我可以安全地线程复制数组的内容。

If not, what other kinds of solutions are available to "block all modifications to the array" without having to go change the classes which instantiate Frame or altering Frame's constructor such that it won't take an array, but only instances of immutable collections (which itself is down another freakin rabbit hole). 如果没有,那么可以使用什么其他解决方案来“阻止对数组的所有修改”,而不必去更改实例化Frame的类或更改Frame的构造函数,从而使其不采用数组,而只采用不可变集合的实例(它本身在另一个freakin兔子洞中)。

Thank you for any guidance you have in this area. 感谢您在此方面的任何指导。

UPDATE: What I want to do is fundamentally not possible. 更新:从根本上讲我想做的事情是不可能的。 And my understanding of locking objects via synchronized was also erred (tyvm glowcoder, Paulo and Brian). 我对通过同步锁定对象的理解也很错误(tyvm glowcoder,Paulo和Brian)。 I am now going to attempt to change the interface on Frame to use List<List<Boolean>> which sure seems like it would be SO much more inefficient. 我现在要尝试更改Frame的接口以使用List<List<Boolean>> ,这肯定看起来效率低得多。 Or I may use Set<XyCoordinate> where the presence of an XyCoordinate means "true". 或者我可以使用Set<XyCoordinate> ,其中Set<XyCoordinate>的存在表示“ true”。 Again, that seems so freakin inefficient, but threadsafe. 再次,这似乎是如此低效,但线程安全。 Ugh! 啊!

I think your best bet is to wrap the array in an object and provide synchronized access to it. 我认为您最好的选择是将数组包装在一个对象中并提供对其的同步访问。 Then you can do the deep copy with the "door shut" to the accessor method. 然后,您可以对访问器方法进行“门关闭”来进行深层复制。

void deepCopy(boolean[][] orig) {
    synchronized(orig) {
        boolean[][] result = new boolean[orig.length][];
        deepCopy(orig,0);
        return result;        
    }
}

/**
 * recursive method to lock all rows in an array, and then copy
 * them in (backwards, by chance)
 */
void deepCopy(boolean[][] orig, int row) {
    if(row == orig.length) return; // end condition
    synchronized(orig[row]) { // lock the row first
        deepCopy(orig,row+1); // lock the next row
        content[row] = new boolean[orig[row].length];
        for(int i = 0; i < content[row].length; i++)
            content[row][i] = orig[row][i];
        // now, do row - 1
    }
}

You seem to have some misunderstanding about object locks. 您似乎对对象锁有一些误解。

A synchronized block (or method) on some object does not avoid any modifications on this object . 某些对象上的synchronized块(或方法) 不能避免对该对象进行任何修改 It only avoids that other threads at the same time are in synchronized blocks or methods of the same object (with the exception on ones in wait() of the same object). 它仅避免了其他线程同时处于同一对象的同步块或方法中(同一对象的wait()中的线程除外)。

So, to be thread-safe here, you have to make sure all your accesses on the array synchronize on the same lock object. 因此,为了确保线程安全,您必须确保对数组的所有访问都在同一个锁对象上同步。

I'd recommend that you look into using the java.util.concurrent package. 我建议您考虑使用java.util.concurrent包。 It has a bunch of useful classes that might help you. 它有一堆有用的类,可能对您有帮助。

For your specific example, you can write a simple ThreadSafe2DArray class: 对于您的特定示例,您可以编写一个简单的ThreadSafe2DArray类:

public class ThreadSafe2DArray {
    private ReadWriteLock readWriteLock;
    private Lock readLock;
    private Lock writeLock;
    private boolean[][] data;

    public ThreadSafe2DArray(int rows, int cols) {
        this.data = new boolean[rows][cols];
        this.readWriteLock = new ReentrantReadWriteLock();
        this.readLock = readWriteLock.readLock();
        this.writeLock = readWriteLock.writeLock();
    }

    public boolean get(int row, int col) {
        try {
            readLock.lock();
            return data[row][col];
        }
        finally {
            readLock.unlock();
        }
    }

    public void set(int row, int col, boolean b) {
        try {
            writeLock.lock();
            data[row][col] = b;
        }
        finally {
            writeLock.unlock();
        }
    }

    public boolean[][] copyData() {
        try {
            readLock.lock();
            // Do the actual copy
            ...
        }
        finally {
            readLock.unlock();
        }
    }
}

You can also expose the raw data array and the read/write locks if you want to allow clients to do their own locking, for example if clients could be reading or updating large numbers of entries in one chunk. 如果要允许客户端进行自己的锁定,例如,如果客户端可以在一个块中读取或更新大量条目,则也可以公开原始数据数组和读/写锁定。

This approach is a lot more flexible than using raw synchronized blocks and may be faster or better for your case, although a lot depends on what sort of contention you expect in practice. 这种方法比使用原始synchronized块要灵活得多,并且对于您的情况可能更快或更佳,尽管在很大程度上取决于您在实践中期望的竞争类型。 The added benefit of this is that you don't need to force anything to be immutable. 这样做的附加好处是您不需要强制任何东西都是不可变的。

Fundamentally, if two threads are ever going to be contending for a resource they must agree on some kind of locking mechanism. 从根本上讲,如果两个线程要争用资源,则它们必须就某种锁定机制达成共识。 If the thread that populates the data array does not play nicely then the only option is to have a wrapper that forces locking. 如果填充数据数组的线程运行不正常,则唯一的选择是使用包装器强制锁定。 If the other thread is well-behaved then you could also synchronize on the top-level array object in both threads, or you can use the expose-locks-and-data approach, or just use the class as presented above and call copyData in your Frame class. 如果另一个线程运行良好,那么您也可以在两个线程中的顶级数组对象上进行同步,或者可以使用暴露锁和数据方法,或者仅使用上面介绍的类并在其中调用copyData 。您的Frame类。

This answer got a lot longer than I intended. 这个答案比我预期的要长得多。 Hopefully it makes sense. 希望这是有道理的。

EDIT: Note that it's important to put the unlock call inside a finally block in case an unexpected RuntimeException (or an Error ) gets thrown somewhere inside the try . 编辑:请注意,重要的是将unlock调用放在finally块中,以防在try内的某个地方抛出意外的RuntimeException (或Error )。 In this case the code is pretty simple so it's probably overkill, but it will make the code more maintainable if this safety net is already in place. 在这种情况下,代码非常简单,因此可能有点过头了,但是如果此安全网已经到位,它将使代码更具可维护性。

EDIT2: I note that you specifically mentioned an irregular array. EDIT2:我注意到您特别提到了不规则数组。 I've left the example as a regular array in the interests of brevity :) 为了简洁起见,我将示例保留为常规数组:)

I really think you're not grasping some core concepts of multithreading. 我真的认为您没有掌握多线程的一些核心概念。

If some other thread has a reference to the array you're passing into the Frame constructor, nothing you do in Frame can help you. 如果其他某个线程引用了要传递给Frame构造函数的数组,则在Frame所做的任何事情都无法帮助您。 That other thread can modify it at will. 该其他线程可以随意对其进行修改。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM