5

我的项目中有以下A类和B类的类结构:

class A {
    private String id;
    private List<B> bs = new ArrayList<B>();

    A(String id){
        this.id = id;
    }

    public List<B> getBs(){
        return bs;
    }

    public void setBs(List<B> bs){
        this.bs=bs;
    }

    public void addBs(List<B> bs){
        this.bs.addAll(bs);
    }

    public void addB(B b){
        this.bs.add(b);
    }
}

class B {
    private String id;
    private List<String> tags;

    B(String id){
        this.id = id;
    }

    public List<String> getTags(){
        return tags;
    }

    public void setTags(List<String> tags){
        this.tags = tags;
    }

    public void addTags(List<String> tags){
        this.tags.addAll(tags);
    }

    public void addTag(String tag){
        this.tags.add(tag);
    }
}

我们还有一个缓存类:

class CacheService {
    private static final ConcurrentHashMap<String, Object> CACHE = new ConcurrentHashMap<String, Object>();

    public static Object get(String id){
        return CACHE.get(id);
    }

    public static void put(String id, Object obj){
        return CACHE.put(id, obj);
    }
}

现在,A 类和 B 类的对象是使用唯一 ID 创建的,并通过 <id, object> 组合放入此缓存中。例如:

A a1 = new A("10");
CacheService.put("10", a1);

A a2 = new A("11");
CacheService.put("11", a2);

B b1 = new B("1");
CacheService.put("1", b1);

B b2 = new B("2");
CacheService.put("2", b2);

B b3 = new B("3");
CacheService.put("3", b3);

B b4 = new B("4");
CacheService.put("4", b4);

此外,我将 B 类对象放在List<B>内部对象a1a2. 需要注意的是,唯一的 B 对象只在任何 A 对象中放置一次

a1.add(b1);
a1.add(b2);

a2.add(b3);
a2.add(b4);

这样,我们可以在 CACHE 中为 A 类和 B 类拥有多个对象。

场景:现在多个线程访问此 CACHE,但其中一些最终会根据用户指定的 ID 获取 A 类对象和其他 B 类对象。这些线程实际上想要读取更新这些对象的信息。

问题:我的要求是,当一个线程访问了 A 类的对象(例如a1)来更新它时,没有其他线程应该能够读取更新 a1以及添加到的所有 B 类对象(b1b2这种情况下)List<B>内部对象,a1直到我完成所有更新a1。请告诉我如何在这种情况下获得锁?

4

3 回答 3

3

要进行深度复制,我建议您Lock在 A 和 B 的每个实例中都有 a ,并让它们都使用lock()unlock()方法实现一些接口,其中类A将获取自己的锁,以及B's. 然后,在使用之前锁定对象,并在完成后解锁。

编辑:所以你的行动方案是:

  1. 创建一个接口,让我们Lockable用两种方法调用它:lock()unlock()
  2. 让 A 和 B 都实现该接口。因此,您的缓存现在将使用Lockable而不是Object
  3. 向 A 和 B 添加一个私有字段

    private final Lock lock = new ReentrantLock ();

  4. 现在Lockable在 B 中的实现将只是在锁上调用相同的方法
  5. 在 A 中,lock()将获取本地锁实例,并迭代 b 的列表并调用它们的lock()方法。相同的unlock()
  6. 现在,每次从缓存中获取对象时,在对它进行任何操作之前,都会调用lock()该对象,然后unlock()在完成时调用。
于 2012-10-22T14:03:00.347 回答
2

关键字将synchronized帮助您解决此问题。您可以将整个方法声明为synchronized,也可以拥有synchronized()块,这些块锁定在您定义的特定键对象上。当synchronized()进入一个块时,在退出该块之前,另一个线程不能访问使用相同密钥对象锁定的其他块。

在此处查看有关同步的 Java 教程。

对于您的示例,您可以执行以下任一操作:

public synchronized void addB(B b) {
    this.bs.add(b);
}

或者

...在您的类中声明一个锁定对象...

private final Object LOCK = new Object();

...并使用它一个synchronized()块:

public void addB(B b) {
    synchronized(LOCK) {
        this.bs.add(b);
    }
}

使用第二个优于第一个的优点是您可以完全、明确地控制哪些代码部分被锁定(而不仅仅是整个方法调用)。在处理并发时,您希望尽可能少地同步以提高效率,因此使用它您可以仅在最低限度上执行同步。

另外,我敢肯定有人会指出您不需要显式声明另一个锁对象,因为您可以使用this关键字在当前对象上进行同步。但是,这个其他 StackOverflow 问题和答案总结了我不建议这样做的原因。

于 2012-10-22T13:49:33.807 回答
1

EDIT2:我打算在编辑问题后完成我的答案,但我刚刚找到了java.util.concurrent.locks.ReentrantReadWriteLock类,我认为它完全符合用户 sohanlal 的需求。

这个类提供了一对锁,一个用于读取操作,可以由多个元素同时拥有,并且本身不是阻塞的。第二个(写入部分)在获取时避免任何读取或写入操作。

那么解决方案如下:

  1. 给容器类A加锁。这个类是读写锁的真正拥有者。
  2. 将锁属性添加到 B 类。该类不拥有锁,但仅引用它以对其进行读锁定。
  3. 将新的 B 对象添加到 A 容器时,为 B 对象提供对容器上 ReentrantReadWriteLock 的引用。从 A 中提取 B 对象时,取消引用从容器“继承”的锁。
  4. 在 A 类中,对修改 B 对象集合的每个操作(添加、删除...)进行写锁定。可选(如果 B 元素的底层集合未同步),不修改集合但访问它的读锁定操作。
  5. 在 B 类中,对每个操作进行读锁定。

代码。这还没有经过测试,但会给出一个应该如何实现的想法:

class A {
    private String id;
    private List<B> bs = new ArrayList<B>();
    private ReentrantReadWriteLock lk = new ReentrantReadWriteLock();

    A(String id){
        this.id = id;
    }

    public List<B> getBs(){
        lk.readLock().lock(); // acquire the read lock, since this operation does not affect A contents
        return bs;
        lk.readLock().unlock();
    }

    public void setBs(List<B> bs){
        lk.writeLock().lock(); // acquire the write lock, which automatically avoids further reading/writing operations
        this.bs=bs;
        for( B elem : bs )
        {
            // internal B elements need a reference to the reading part of the lock
            elem.setLock(lk.readLock()); 
        }
        lk.writeLock().unlock();
    }

    public void addBs(List<B> bs){
        [...] // similar approach that in setBs
    }

    public void addB(B b){
        [...] // similar approach that in setBs
    }

    public void deleteB( B elem ) // Or whatever notation you want
    {            
        lk.writeLock().lock(); // acquire the write lock
        B internalElem = bs.get(bs.indexOf(elem));             
        if( internalElem != null )
        {
            bs.remove(internalElem);
            bs.unsetLock();
        }
        lk.writeLock().unlock();
    }
}

class B {
    private String id;
    private List<String> tags;
    private Lock lk;

    B(String id){
        this.id = id;
        lk = null;
    }

    public void setLock(Lock l){ lk = l; } // put additional controls if you want
    public void unsetLock()
    { 
        lk = null; 
    }

    private void lockInternal()
    {
        if(lk!=null){ lk.lock(); }
    }

    private void unlockInternal()
    {
        if(lk!=null){ lk.unlock(); }
    }

    public List<String> getTags(){
        List<String> ref = null;            
        lockInternal();
            [...] //internal operations
        unlockInternal();
        return ref;
    }

    public void setTags(List<String> tags){
        [...] // similar approach that in getTags
    }

    public void addTags(List<String> tags){
        [...] // similar approach that in getTags
    }

    public void addTag(String tag){
        [...] // similar approach that in getTags
    }
}

原始答案:

Jeff 的回答是一个很好的起点,因为它似乎解决了触摸 A 型对象并修改其组成的问题。但是,我们仍然有一个与 A 上包含的 B 类对象有关的问题。假设我们有一个 A 类对象,其中包含几个 B 类元素:

A a1 = new A("10");
B b1 = new B("myB1");
B b2 = new B("myB2");
B b3 = new B("myB3");
a1.add(b1);
a1.add(b2);
a1.add(b3);

问题是,如果你正在操作a1,你想锁定任何操作b1b2并且b3。你怎么能确定呢?

我看到的唯一解决方案是 A 容器内的所有 B 元素共享一些公共 Lock 变量。在调用 over 的写相关操作时a1,它获取锁,避免了对 B 元素的 ANY 操作。请注意,对 B 元素的任何操作都不需要实际获取锁……只是为了检查它是否已被 A 型容器获取。

很多缺点/考虑:

  • 性能:对 B 对象的每个操作都将涉及检查锁(这可能非常昂贵)
  • 操作限制:如果一个 B 对象可以添加到多个 A 类容器中,这将变得更加复杂
  • 实现复杂度:当你在里面插入一个 B 对象时a1,你必须为 B 提供共享锁。提取时,必须解除对锁的引用。如果容器 A 被删除会怎样?你必须照顾这种事情。

编辑:我忘了提到这个机制,在编写 A 型容器时,不会“停止”或考虑对包含的 B 型元素的持续操作。

于 2012-10-22T14:09:52.200 回答