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

Tra*_*ers 2 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);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

当我想要专门处理这样的对象时,我会在这样的对象上使用synchronized:

synchronized(obj) {
    /* do something exclusively on obj (work type I) */
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,这一直一直运作良好.新要求是有I型和II型独家作品.类型I将保留对象,类型II应在完成工作后删除对象.如果我按照以下方式做某事:

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

虽然该对象已从地图中删除,但我可能会将某些对象授予某些类型的工作.所以基本上,类型I工作的synchronized(obj)应该被一些新的信号量所取代,如果之前授予了类型II的工作,它就会将对象重新加入到地图中.对象应该只在没有未处理的同步时离开地图.

如果没有看到物体,那将是最好的.我会使用只有名称的API.这些对象仅用于维护名称的某些状态.但是,在完成第二类工作后,HashMap应该从名称中解放出来.但是在I型或II型工作期间,不应该锁定HashMap.

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

再见

Bor*_*der 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. 做工作.在TypeIIdata完成后删除映射的情况下.
  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));
}
Run Code Online (Sandbox Code Playgroud)

我使用Lombok注释来减少杂乱的数量.

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

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

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