Java中用于匹配数据的原子块

Java中用于匹配数据的原子块,java,synchronization,atomic,Java,Synchronization,Atomic,我正在编写的一个库有一个实现二维映射的类,并且还为行/列视图提供了更小的映射,以便高效地阅读。到目前为止,所有方法都已被覆盖,因此主映射中的任何更改都会反映在子映射中,反之亦然。问题出现在并发操作中 理想情况下,从主地图中删除项目将同时从相应的行和列地图中删除项目,但这当然是不可能的。例如,在my put函数中: public synchronized Cell put(Duple<Integer, Integer> key, Cell arg1){ //preprocess

我正在编写的一个库有一个实现二维映射的类,并且还为行/列视图提供了更小的映射,以便高效地阅读。到目前为止,所有方法都已被覆盖,因此主映射中的任何更改都会反映在子映射中,反之亦然。问题出现在并发操作中

理想情况下,从主地图中删除项目将同时从相应的行和列地图中删除项目,但这当然是不可能的。例如,在my 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;
}
公共同步小区put(双工键,小区arg1){
//预处理、检测行/列、创建行/列(如果尚未出现)等。
Cell outcell=super.put(键,arg1);
rowArr.putPriv(key.getElem2(),arg1);
colArr.putPriv(key.getElem1(),arg1);
arg1.assignCell(此,键);
返回外单元;
}
同时读取映射是完全可以接受的,即使并发修改也不是问题(除了创建/删除需要同步删除和放置的行/列),而是修改的4个阶段(super.put、行和列放置以及单元格位置更新)需要原子化,以确保不可能读取不匹配的数据

我有什么选择?根据我的搜索发现,不可能在Java中创建语句的原子序列,除非我同步所有函数(这会阻止并发读取,并且我需要对多个项进行锁定),否则同步将无法工作。我知道基本信号量概念的原理(但不是特别熟悉),但没有发现任何简单的方法可以在没有大量复杂性的情况下锁定写信号量,特别是如果我不想大量等待一个写槽的话。我还有什么其他选择


注意:由于我参与的项目,我不能使用groovy之类的派生语言,但只能使用标准Java1.6u24,没有第三方库

调用相关方法时,可以动态创建行/列视图。这样,您就摆脱了导致问题的四向更新


(您将以简单性换取效率的降低,但如果您不在高性能环境中,这可能不是一个问题。即使如此,也值得对解决方案进行基准测试,看看这是否真的是一个糟糕的举动)。

我建议您不要对整个方法进行同步
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;
    }
}
类ConcurrMattr{
私有ConcurrentHashMap锁=
新的ConcurrentHashMap();
公共单元put(单元Coords键,单元arg1){
//获取或创建特定单元格的锁(保证其唯一性)
Lock Lock=this.locks.putIfAbsent(coords.hash%64,new ReentrantLock());
//64个线程可以同时修改矩阵的不同单元
试一试{
//只锁定特定单元格
lock.lock();
//你需要手机吗
返回。。。;
}最后{
//解锁单元
lock.unlock();
}
}    
}
//表示单元坐标的不可变类
类CellCoords{
公共最终int x;
公共财政;
公共最终整数散列;
公共蜂窝电话(int x,int y){
这个.x=x;
这个。y=y;
this.hash=this.calcHash();
}
私有内部calcHash(){
int result=31+this.x;
返回31*result+this.y;
}
}
因此,您可以同步特定单元格上的
读/写
方法,而矩阵的其他部分将可供其他线程访问

查看javadoc以了解和

p.S.
您可能会注意到,
CellCoords
field
hash
的类型为
int
。要避免将锁映射大小增加到2^31,必须限制散列的范围。例如:
(coords.hash%64)
-只允许64个并发线程处理整个矩阵


p.p.S.对您来说可能是一篇有趣的文章:

有趣的想法,但问题是行/列映射很快就过时了。其想法是,根据使用情况,挑选一行并花一些时间处理它是很有价值的,就像从数据库中获取记录一样。好吧,假设这个库计划使用的一半是单线程的,但是如果它将成为一个库,它需要健壮。我假设你会在将地图返回给用户之前克隆它们?在这种情况下,是的,它们的日期是从你克隆它们的那一刻起。如果不是,那么如果每个调用程序都在您的地图上,那么您肯定会遇到一个主要的线程安全问题?线程安全是我提出这个问题的原因之一。虽然我理解这个理论,但我并不自然地以并行的方式思考,因此我没有编写它的经验。斯泰姆关于锁的回答可能导致了我正在寻找的系统。这可能正是我要寻找的解决方案!这确实意味着我的get函数中需要锁,但至少它们是干净且并发的。@K.Barad,您可以单独使用读锁和写锁,前提是您可以保证不会出现竞争条件:当一个线程写入数据,而其他线程从同一单元格读取数据时。否则,读线程可能会从我读取的内容中获取不一致的数据。读写锁允许获取多个读锁,但写锁是独占的。当持有写锁时,没有其他人可以读取,这意味着写锁和读锁处于主从式关系中。对我来说,这听起来像是实现我想要的完美行为,除非你看到进一步的风险?我将有写锁通过所有的put和remove函数无论如何,所以90%的时间