为并发删除的名称维护唯一对象

时间:2014-03-03 00:02:23

标签: java concurrency hashmap

我正在使用以下编程习惯用法。我保持同步 具有名称与对象关联的HashMap。要查找 名称的对象我使用以下代码:

MyObject getObject(String name) {
   synchronized(map) {
      MyObject obj = map.get(name);
      if (obj == null) {
         obj = new MyObjec();
         map.put(name, obj);
      }
   }
}

当我想专门研究这样一个对象时我 将在这样的对象上使用synchronized:

synchronized(obj) {
    /* do something exclusively on obj (work type I) */
}

到目前为止,这种方法一直运作良好。新的 要求是I型和II型独有 作品。类型I将保留对象,类型II应删除 完成工作后的对象。如果我做某事 以下内容:

synchronized(obj) {
    /* do something exclusively on obj (work type II) */
}
synchronized(map) { /* not good! */
   map.remove(obj);
}

我可能会给某些类型的对象授予某些类型,但是 对象已从地图中删除。所以基本上 应该替换类型I工作的synchronized(obj) 通过一些新的信号量将对象重新加入到地图中 如果以前批准了第二类工作。分别 对象应该只在没有同步时离开地图 正在等待它。

如果没有看到物体,那将是最好的。我会去 使用仅包含名称的API。对象是唯一的 用于维护名称的某些状态。但是HashMap 在II型工作完成后应该从名称中解脱出来 完成。但在I型或II型工作期间,HashMap 不应该被锁定。

任何想法如何做到这一点?这是一种已知的模式吗?

再见

6 个答案:

答案 0 :(得分:6)

要求似乎是这样的:

  • 有一个Map<String, Object>是缓存。
  • 池中有许多工作线程访问缓存
  • 某些类型的工作要求缓存中的对象在完成后无效

首先,您需要一个ConcurrentHashMap<String, Lock> keys。此Map将存储我们将使用锁定密钥的String密钥和Lock对象之间的关系。这允许我们替换key -> value映射而不锁定整个数据Map

接下来,您需要一个ConcurrentHashMap<String, Object> data。这个Map将存储实际的映射。

使用ConcurrentHashMap而不是普通的原因是它是线程安全的。这意味着不需要手动同步。实现实际上将Map划分为扇区,并且仅锁定所需的扇区以执行操作 - 这使其更有效。

现在,逻辑将是

  1. putIfAbsentReentrantLockkeys。这将以线程安全的方式检查key是否已存在锁定。如果不是,则添加新的,否则检索现有的。这意味着每个键只能有一个锁
  2. 获取锁定。这意味着您可以获得对映射的独占访问权。
  3. 做好工作。如果TypeII在完成后从data删除了映射。
  4. 解锁。
  5. 代码看起来像这样:

    private final ConcurrentHashMap<String, Object> data = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, Lock> keys = new ConcurrentHashMap<>();
    private final ExecutorService executorService = null; //obviously make one of these
    
    @RequiredArgsConstructor
    private class TypeI implements Runnable {
    
        private final String key;
        private final Work work;
    
        @Override
        public void run() {
            final Lock lock = keys.putIfAbsent(key, new ReentrantLock());
            lock.lock();
            try {
                final Object value = data.get(key);
                work.doWork(value);
            } finally {
                lock.unlock();
            }
        }
    }
    
    @RequiredArgsConstructor
    private class TypeII implements Runnable {
    
        private final String key;
        private final Work work;
    
        @Override
        public void run() {
            final Lock lock = keys.putIfAbsent(key, new ReentrantLock());
            lock.lock();
            try {
                final Object value = data.get(key);
                work.doWork(value);
                data.remove(key);
            } finally {
                lock.unlock();
            }
        }
    }
    
    public static interface Work {
    
        void doWork(Object value);
    }
    
    public void doTypeIWork(final String key, final Work work) {
        executorService.submit(new TypeI(key, work));
    }
    
    public void doTypeIIWork(final String key, final Work work) {
        executorService.submit(new TypeII(key, work));
    }
    

    我使用了Lombok注释来减少混乱。

    这个想法是最小化或几乎消除公共资源锁定的数量,同时仍然允许Thread获得对特定映射的独占访问权。

    要清除密钥Map,您需要保证当前没有正在进行的工作,并且在清理期间没有Thread尝试获取任何锁定。您可以通过尝试获取相关锁,然后从键映射中删除映射来实现此目的 - 这将确保当时没有其他线程正在使用锁。

    您可以运行一个计划任务,每隔X分钟从地图中清除20个键。如果您将其实现为LRU缓存,那么它应该相当干净。 Google Guava提供您可以使用的an implementation

答案 1 :(得分:1)

1)第一种是使用哈希来存储你的数据对象objHash。

2)你需要一个额外的锁来确保对objHash.Type2的type1和type2操作的原子执行是写操作,而Type1是读操作。您可以使用读写锁并将锁存储在哈希表lockHash。

3)为了确保数据对象上的type1,type2的原子操作,你必须将你的     在同步语句中的type1 / 2操作,用于锁定此数据对象。

public class ConDeleteHash {
    ConcurrentHashMap <String, Object> objHash = new ConcurrentHashMap <String, Object> ();
    ConcurrentHashMap <String, ReentrantReadWriteLock> lockHash = new ConcurrentHashMap <String, ReentrantReadWriteLock> ();
    void Type1Op(String name) {
        ReadWriteLock rwl = lockHash.get(name);
        if(rwl==null) return;
        Lock lock = rwl.readLock();
        lock.lock();
        Object obj = objHash.get(name);
        if(obj==null) return;
        synchronized(obj) {
             System.out.println("TYPE1 to :"+ obj.toString());
        }
        lock.unlock();      
    }
    void Type2Op(String name) {
        ReadWriteLock rwl = lockHash.get(name);
        if(rwl==null) return;
        Lock lock = rwl.writeLock();
        Object obj = objHash.get(name);
        synchronized(obj) {
            System.out.println("TYPE2 to :"+ obj.toString());
        }
        lockHash.remove(name);
        objHash.remove(name);
        lock.unlock();
    }
    void add(String name, Object obj) {
        if(lockHash.get(name)!=null) return;
        objHash.put(name, obj);
        lockHash.put(name, new ReentrantReadWriteLock());
    }
}

答案 2 :(得分:1)

从地图中删除延迟

... class MyObject{
    boolean active = true;
    ...
}


synchronized(obj) {
    if(obj.active){
        /* do something exclusively on obj */
        obj.active = false; //or not
    }
}


MyObject getObject(String name) {
   synchronized(map) {
      MyObject = map.get(name);
      if (obj == null) {
         obj = new MyObjec();
         map.put(name, obj);
      }else{
         synchronized(obj){
             if(!obj.active){
                 //any remove action here
                 obj = new MyObjec();
                 map.put(name, obj); // no previous obj in map
             }
         }
   }
}

答案 3 :(得分:1)

这个怎么样......鲍里斯蜘蛛的略微修改版本。

将ConcurrentHashMap用于工作人员的主类

public class Concurrent {
        // Hash map to hold workers
        final ConcurrentHashMap<String, Work> jobs = new ConcurrentHashMap<>();

工作界面

    interface Work{
         void doWork(Object value);
    }

Blocking Works的基类。表示此实例只能完成一项工作

 abstract class BaseWork implements Work {

    String name;

    Lock lock = new ReentrantLock();

    BaseWork(String name){
        this.name = name;
    }

    @Override
    public void doWork(Object value) {
        lock.lock();   // lock the following block
        try{
            if (jobs.get(name) != null) {    // Check in case there are waiting threads to perform work on this instance which is removed by completed Type11 Work
                performTask(value);
                System.out.println("Job Completed");
            }else{
                    jobs.putIfAbsent(name, new Type2Work(name)).doWork(value); // if new job has to be trigger. Note this section only possible when Type2Work, so created Type2Work

                System.out.println("Removed.. Job terminated");
            }
        }finally{
            lock.unlock(); // unlock this block , so other threads can start working
        }
    }
    abstract void performTask(Object value);    // Actual Job 
}

这里,name与concurrentHashMap中的key相同。 一旦doWork调用,它就会锁定执行实际工作的块。

Type1和Type 2 Implementation

  class Type1Work extends BaseWork{

    Type1Work(String name) {
        super(name);
    }

    @Override
    void performTask(Object value) {
        // Do type 1 Work
    }

}



   class Type2Work extends BaseWork{

    Type2Work(String name) {
        super(name);
    }

    @Override
    void performTask(Object value) {
        // Do Type 2 work.
        jobs.remove(name);
    }
}

非阻止工作 - 类型111(doWork可以执行工作而不在线程之间共享任何信息)

    class NonLockingWork implements Work {

    @Override
    public void doWork(Object value) {
        // Do thread safe non blocking Work ( Type 111)
    }
}

最后阻止将作品加载到Map

          String key = "type1-name1";
    Work work = jobs.putIfAbsent(key, new Type1Work(key));
    work.doWork(new Object());



}

答案 4 :(得分:1)

您可以使用AtomicInteger跟踪对象上正在执行的任务数。然后,对于类型II任务,仅在没有正在进行的任务时删除对象:

class MyObject {
   private AtomicInteger worksInProgress = new AtomicInteger(0);
   public int incWIP() {
      return worksInProgress.incrementAndGet();
   }
   public int decWIP() {
      return worksInProgress.decrementAndGet();
   }
   public int getWIP() {
      return worksInProgress.get();
   }
   ...
}

MyObject getObject(String name) {
   synchronized(map) {
      MyObject obj = map.get(name);
      if (obj == null) {
         obj = new MyObject();
         map.put(name, obj);
      }
      obj.incWIP(); // assume you're doing work on this starting now
   }
}

工作类型我看起来像:

MyObject obj = getObject(name);
synchronized(obj) {
   obj.workI();
}
obj.decWIP(); // finished doing work type I

类型II看起来像:

MyObject obj = getObject(name);
synchronized(obj) {
   obj.workII();
}
if (obj.decWIP() == 0) { // finished with this work and all others
   synchronized(map) {
      // double-check the value because we checked previously without the map lock
      if (obj.getWIP() == 0) {
         map.remove(obj);
      }
   }
}

答案 5 :(得分:0)

这个问题的逻辑存在问题:如果允许用户在类型2操作后请求类型1操作,为什么要从地图中删除对象?如果允许用户在类型2之后提交类型1,则在删除对象之后将始终存在请求类型1的情况。在哪种情况下你为什么删除它?

客户端在第二类操作之前只能提交第一类操作的问题,但是您不能保证一方将由执行程序服务执行另一方操作?在这种情况下,使用优先级执行程序服务并提交优先级高于类型2的类型1,因为这样可确保类型1始终在类型2之前启动(如果两者都处于未决状态)。由于类型2在类型1完成之前无法启动,这意味着在类型2之前提交的类型1提交类型1后,始终执行删除。

这感觉就像其他地方糟糕的程序设计导致了一个不可解决的困境。如果你能解释这些奇怪的规范是如何产生的,那么我们就可以制定出更持久的解决方案。

此外,在这种类型的并发中,不要从地图中删除对象,用单个对象替换它们是对象的子类,并使用doWork方法检查此单例,这样更可靠比空检查,因为可以出于很多原因创建空值,但是你的单例对象被传递以便在特定原因下工作,这意味着在开发后期更容易进行错误跟踪。