复合编辑的并发性

时间:2009-12-27 16:16:42

标签: java concurrency

我有这些类用于创建我想在运行时存储的对象

Class Person
    String name
    Pet[] pets

Class Pet
    String name
    Person[] owners
    boolean neutered

起初我使用这些HashMaps来存储它们

HashMap people
HashMap pets

但是我想让实现并发,所以我改变了这些地图

ConcurrentHashMap people
ConcurrentHashMap pets

我使用"compareAndSet in a while loop" pattern进行原子更新。

但我仍然遇到问题,因为People地图中的每个人都在Pets地图中关联了宠物。为了保持更新的原子性,我添加了ReentrantReadWriteLocks这样我就可以使用关联的People对象同时更新Pet个对象。

ConcurrentHashMap people
ConcurrentHashMap peopleLocks
ConcurrentHashMap pets
ConcurrentHashMap petLocks

现在当我对多个记录执行编辑时,我首先获取所有写锁,然后进行编辑,最后释放写锁。这样可确保在进行更新时无法读取。

changePetNames(Person person, Pets[] pets, String[] names) {
    // get Person lock
    // get Pet locks
    // make updates
    // release locks
}

neuter(Pets[] pets) {
    // get Pet locks
    // make updates
    // release locks

然后我将所有编辑方法同步到一个对象上,这样竞争编辑就不会死锁

private final Object leash = new Object();
changePetNames(Person person, Pets[] pets, String[] names) {
    synchronized(leash) {
        // get Person lock
        // get Pet locks
        // make updates
        // release locks
    }
}

neuter(Pets[] pets) {
    synchronized(leash) {
        // get Pet locks
        // make updates
        // release locks
    }
}

所以现在我有运行时存储,允许并发读取和同步写入。我的问题是,是否有办法在保护人与宠物之间的关系的同时兼顾写入。

2 个答案:

答案 0 :(得分:2)

您可以在People person对象上进行同步,而不是在皮带对象上进行同步。这允许同时改变不同的人和他们的宠物,同时阻止对一个人和她的宠物的同时变化。

PS,从它的外观来看,你的锁定系统似乎有点过于复杂。假设人 - 宠物是1对多的关系,一个人可以拥有许多宠物,但任何宠物只有一个拥有者,只有同步到人对象可能就是你需要的全部。

PS2,命名很重要且您的班级名称是复数,我认为使用PersonPet代替PeoplePets会更好地描述概念更容易理解。

修改neuter这样只需要宠物而不需要更改所有者数据的方法会使它们并发,同步宠物,但这意味着:

  • 当你编辑一个人和她的宠物时,你需要同时对这个人和宠物进行同步以防止宠物只改变
  • 有时可以锁定宠物,而有宠物的人也需要锁定

当一个线程有一个宠物锁并试图让一个人锁定而另一个线程有人锁定并试图获得宠物锁时,上述情况可能导致死锁情况。我的解决方案是在所有者上进行同步,即使只需要更改宠物,这意味着changePetNames和neuter看起来像:

changePetNames(Person person, Pets[] pets, String[] names) {
    synchronized(person) {
        // make updates
    }
}

neuter(Pets[] pets) {
    for (Pets pet: pets) {
        // make sure pets owner exists
        synchronized(pet.getOwner()) {
            // make updates
        }
    }
}

这样,如果您从未在不同的人身上嵌套同步操作,则不会发生死锁。

<强> EDIT2 当宠物的主人是多对多的关系时,你需要同步一个人和宠物的独特组合的代表,这将重现你已经获得更新的写锁。我的结论是,如果可以确保不发生死锁,则不需要额外的同步租约。

如果两个线程想要获取另一个之前已获得的锁,则会发生死锁,因此,如果您可以确保始终以相同的顺序获取锁,则此问题不会发生。

如果您要为Person和Pet添加唯一的创建ID,并且每个更新集的获取锁定始终按递增顺序排列,则不会发生死锁情况:

changePetNames(Person person, Pets[] pets, String[] names) {
    // sort Person ID's
    // get Person lock in ID sequence
    // sort Pet ID's
    // get Pet locks in ID sequence
    // make updates
    // release locks
}

应该这样做。

答案 1 :(得分:1)

  

然后我有了所有的编辑方法   同步一个对象,这样   竞争编辑不会锁定每个   其他的。

现在所有编辑都“互相锁定”。这是一种改进方式?

我不明白为什么你不能简单地省略那个同步。对Person的ReadWrite锁定的获取已经防止了冲突的并发更新(除非宠物更改了所有者,可以通过原始所有者的其他同步来处理)。

编辑:啊,“互相锁定”意味着“死锁”......

Wikipedia's article on the Dining Philosophers problem讨论了几种避免死锁的典型技术。在您的情况下,最简单的解决方案可能是“资源层次结构解决方案”,其中包含要同步的对象的总订单。例如,如果按升序(唯一)id的顺序锁定对象,然后执行操作,然后释放所有锁,则不会发生死锁。 (因为持有“最高”锁的线程不会受到任何其他线程的阻碍。)