我正在编写的库有一个实现二维地图的类,并且为了有效读取,还为行/列视图提供了较小的地图。到目前为止,所有方法都已被覆盖,因此主映射中的任何更改都会在子映射中进行镜像,反之亦然。问题出现在并发操作中。
理想情况下,从主地图中删除项目将同时从相应的行和列地图中删除该项目,但这当然是不可能的。例如在我的put函数中:
public synchronized Cell put(Duple<Integer, Integer> key, Cell arg1){
//preprocessing, detecting the row/col, creating row/col if not present yet, etc.
Cell outcell = super.put(key, arg1);
rowArr.putPriv(key.getElem2(), arg1);
colArr.putPriv(key.getElem1(), arg1);
arg1.assignCell(this, key);
return outCell;
}
虽然同时读取映射是完全可以接受的,但即使并发修改也不是问题(除了创建/删除需要删除和放置同步的行/列),但修改的4个阶段( super.put,行和列put,以及单元格位置更新)需要是原子的,以确保无法读取不匹配的数据。
我有什么选择?就我从搜索中发现的那样,不可能在Java中创建一个原子序列的语句,除非我同步所有函数(这会阻止并发读取,并且我需要锁定多个函数),同步不会起作用项目)。我知道基本信号量概念的原理(但并未特别实践),但是没有看到任何简单的方法来制作一个没有大量复杂性的锁定写入信号量,特别是如果我不想大量等待写作槽。我还有哪些其他选择?
注意:由于我正在进行的项目,我不能使用诸如groovy之类的衍生语言,但只能使用标准的Java 1.6u24,没有第三方库。
答案 0 :(得分:1)
您可以在调用相关方法时动态创建行/列视图。这样,您就可以摆脱导致问题的四方更新。
(你会简单地交易简单以降低效率,但如果你不在高性能环境中,这可能不是问题。即使这样,也值得对解决方案进行基准测试,看看它是否真的如此一个糟糕的举动。)
答案 1 :(得分:1)
我建议您不要对整个方法put
进行同步。但仅在特定单元格上同步。在下面的代码中,我将描述如何执行此操作:
class ConcurrMattr {
private ConcurrentHashMap<Integer, Lock> locks =
new ConcurrentHashMap<Integer, Lock>();
public Cell put( CellCoords key, Cell arg1 ) {
// get or create lock for specific cell (guarantee its uniqueness)
Lock lock = this.locks.putIfAbsent( coords.hash % 64, new ReentrantLock() );
// 64 threads may concurrently modify different cells of matrix
try {
// lock only specific cell
lock.lock();
// do all you need with cell
return ...;
} finally {
// unlock cell
lock.unlock();
}
}
}
// Immutable class to represent cell coordinates
class CellCoords {
public final int x;
public final int y;
public final int hash;
public CellCoords( int x, int y ) {
this.x = x;
this.y = y;
this.hash = this.calcHash();
}
private int calcHash() {
int result = 31 + this.x;
return 31 * result + this.y;
}
}
因此,您可以在特定单元格上同步read/write
方法,而其他线程可以访问矩阵的其他部分。
P.S。
您可能会注意到,CellCoords
字段hash
的类型为int
。为了避免锁定地图大小增长到2 ^ 31,您必须限制哈希范围。例如:(coords.hash % 64)
- 只允许64个并发线程使用整个矩阵。
P.P.S。可能是有趣的文章:http://www.ibm.com/developerworks/java/library/j-jtp08223/