有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java通过并发删除维护名称的唯一对象

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

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

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

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类工作的同步(obj) 通过某种新的信号量将对象重新连接到地图 如果之前授予了II类工作。分别地 对象只应在没有同步时离开贴图 正在等待

最好是看不到物体。我会去的 使用只包含名称的API。这些物体只是 用于维护名称的某些状态。但是HashMap 在完成II类工作后,应将其从名称中解放出来 完整的。但在第一类或第二类工作中 不应该被锁定

有什么办法吗?这是已知的模式吗

再见


共 (3) 个答案

  1. # 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
                 }
             }
       }
    }
    
  2. # 2 楼答案

    要求似乎是这样的:

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

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

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

    之所以使用ConcurrentHashMap而不是普通的,是因为它是线程安全的。这意味着不需要手动同步。该实现实际上将Map划分为多个扇区,并且只锁定执行操作所需的扇区——这使得它更加高效

    现在,逻辑将是

    1. putIfAbsent一个新的ReentrantLock变成keys。这将以线程安全的方式检查key的锁是否已经存在。如果没有,将添加一个新的,否则将检索现有的。这意味着每把钥匙只有一把锁
    2. 获得一把锁。这意味着您可以以独占方式访问映射
    3. 工作吧。在TypeII的情况下,在完成后从data移除映射
    4. 打开锁

    代码如下所示:

    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缓存,那么它应该相当干净。谷歌番石榴提供an implementation你可以使用

  3. # 3 楼答案

    这个怎么样。。蜘蛛鲍里斯的一个稍加修改的版本

    带有ConcurrentHashMap的主类,用于工作人员

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

    工作界面

        interface Work{
             void doWork(Object value);
        }
    

    用于阻塞工作的基类。这意味着这个实例只能完成一项工作

     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 
    }
    

    在这里,名称将与concurrentHashMap中的键相同。 doWork一打电话,它就会锁定执行实际工作的区块

    类型1和类型2实现

      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)
        }
    }
    

    最后一个块将作品加载到地图中

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