【发布时间】:2011-03-28 01:14:57
【问题描述】:
我在编写 Java 类时偶然发现了一个烦恼;我不知道如何使复制二维数组线程安全。这是该类的简单版本:
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;
}
}
然而,上述方法private boolean[][] deepCopy(boolean[][] content) 的实现实际上并不是线程安全的。在此方法尝试复制时,可能正在由另一个线程主动修改该数组。当然,我已经防范了最滥用的情况,在基本阵列上使用synchronized。但是,这不会导致二维数组实例的集合被锁定。并且可以在复制过程中对其进行修改。
是否有某种方法可以为每个基本数组 (content) 和子数组 (content[0], content[1], ..., content[content.length - 1]) 收集对象锁,这样我就可以调用类似synchronized(objectsToLockSimultaneouslyList) 的东西,它会同时按列表的顺序锁定所有对象。如果是这样,我可以线程安全地复制数组的内容。
如果不是,还有哪些其他类型的解决方案可用于“阻止对数组的所有修改”,而无需更改实例化 Frame 的类或更改 Frame 的构造函数,这样它就不会使用数组,而只需要实例不可变的集合(它本身就是另一个怪诞的兔子洞)。
感谢您在这方面的任何指导。
更新:
我想做的事情基本上是不可能的。而且我对通过同步锁定对象的理解也是错误的(tyvm glowcoder,Paulo 和 Brian)。我现在将尝试将 Frame 上的界面更改为使用List<List<Boolean>>,这肯定看起来效率要低得多。或者我可以使用Set<XyCoordinate>,其中 XyCoordinate 的存在意味着“真实”。再一次,这似乎非常低效,但线程安全。啊!
【问题讨论】:
-
你想要的都是不可能的。替换数组,例如如果可以的话,请来自 Guava 的
ImmutableList。或者,不要将它们暴露给其他线程,给它们一个副本以供使用。接受外部论据时使用防御性文案。 -
@maaartinus:知道了。只需阅读其他答案和 cmets。所以,基本上没有可能的线程安全方式来使用数组作为我的构造函数的参数。这意味着我只需要更改我的构造函数以不使用数组。当然,这意味着我必须找到某种方法来验证我正在传递一个不可变的布尔值列表的不可变列表,这似乎非常低效。
-
@chaotic3quilibrium 对,List
- > 很糟糕。 ImmutableList
呢?前者存在于 Guava 中,后者将是 BitSet 的简单包装器。 -
也许你对“线程安全”的理解是罪魁祸首。假设您始终正确同步,您可以以线程安全的方式使用任何对象。不可变类使这一切变得微不足道,但它们并不总是必要的。不要将程序的其余部分视为您的敌人,试图同时修改您的对象以给您带来麻烦。
-
实际上,您可以将 boolean[][] 包装在一个线程安全的包装器中,并使用
get(int x, int y)和set(int x, int y, boolean value)等同步方法。只是不要实现任何数组返回方法,因为它会返回一个可变的东西。或者可以这样做,但返回一个克隆,请参阅here。
标签: java arrays thread-safety copy