【问题标题】:ConcurrentSkipListMap how to make remove and add calls atomicConcurrentSkipListMap 如何使删除和添加调用原子化
【发布时间】:2018-02-28 16:34:06
【问题描述】:

我有 N 个添加值的线程和一个删除线程。我正在考虑如何同步添加到现有值列表和删除列表的最佳方式。

我猜以下情况是可能的:

 thread 1 checked condition containsKey, and entered in else block
 thread 2 removed the value
 thread 1 try to add value to existing list, and get returns null

我认为我可以使用的唯一方法是按地图值同步,在我们的例子中是添加和删除时的列表

    private ConcurrentSkipListMap<LocalDateTime, List<Task>> tasks = new ConcurrentSkipListMap<>();

    //Thread1,3...N
    public void add(LocalDateTime time, Task task) {
        if (!tasks.containsKey(time)) {
            tasks.computeIfAbsent(time, k -> createValue(task));
        } else {
             //potentially should be synced
            tasks.get(time).add(task);
        }
    }
    private List<Task> createValue(Task val) {
        return new ArrayList<>(Arrays.asList(val));
    }

    //thread 2
   public void remove()
    while(true){
        Map.Entry<LocalDateTime, List<Task>> keyVal = tasks.firstEntry();
        if (isSomeCondition(keyVal)) {
            tasks.remove(keyVal.getKey());
            for (Task t : keyVal.getValue()) {
                //do task processing
            }
        }
    }
   }

【问题讨论】:

  • 您需要ConcurrentSkipListMap吗? ConcurrentHashMapcomputecomputeIfAbsent 等操作提供原子性保证,这在这里可能非常有用。
  • @shmosel 我猜是因为我想存储按 LocalDateTime 排序的记录
  • @shmosel 与 CSLM 相比,我不清楚 CHM 保证在这里能真正帮助您。
  • 我当然希望我错了,但我知道没有任何东西可以满足您的需求,没有外部/额外的同步。

标签: java multithreading java-8 java.util.concurrent


【解决方案1】:

关于add 部分,您真的倾向于使用merge,但文档对此非常清楚 - 说它不能保证以原子方式发生。

我会用merge 替换您的add,但处于锁定状态

SomeLock lock ... 
public void add(LocalDateTime time, Task task) {
    lock.lock();
    tasks.merge...
    lock.unlock();
}

remove 方法也是如此。但是,如果您在锁定下做事,则首先不需要ConcurrentSkipListMap

另一方面,如果您可以更改为 ConcurrentHashMap - 例如,它具有 merge 是原子的。

【讨论】:

  • hah) 所以我想我需要用简单的 TreeMap 替换它。顺便说一句,如果我的 key 不是 List 而是单个 Object,我猜 CSLM 不需要同步,你怎么看?
  • 在这种情况下,我完全不明白我们为什么需要它
  • 对不起,我的意思是 ConcurrentSkipListMap
  • @Sergey 如果我理解正确,您的意思是拥有ConcurrentSkipListMap&lt;LocalDateTime, String&gt; 并对其执行操作,如put, remove 等?当然,这些操作是线程安全的,因为文档明确说明了这一点。还是你的意思是我没有关注的其他内容?
  • @Sergey 这就是重点。 ConcurrentSkipListMap individual 操作是线程安全的,当你 chain 它们时 - containsKey -&gt; computeIfAbsent ,你现在显然有一个竞争条件。
【解决方案2】:

您的 remove() 方法应该做什么并不完全清楚。在当前的形式中,它是一个无限循环,首先,它会遍历头元素并删除它们,直到头元素不满足条件,然后,它将反复轮询该头元素并重新评估条件.除非,它设法删除所有元素,在这种情况下,它会以异常方式退出。

如果你想处理当前地图中的所有元素,你可以简单地循环它,弱一致的迭代器允许你在修改它的同时继续;您可能会注意到正在进行的并发更新。

如果你只想处理匹配的头元素,你必须插入一个条件,返回调用者或​​者让线程进入睡眠状态(或者最好添加一个通知机制),以避免重复烧毁CPU测试失败(甚至在地图为空时抛出)。

除此之外,在确保功能之间没有干扰的情况下,您可以使用ConcurrentSkipListMap 实现这些操作。假设remove 应该处理所有当前元素一次,实现可能看起来像

public void add(LocalDateTime time, Task task) {
    tasks.merge(time, Collections.singletonList(task),
        (l1,l2) -> Stream.concat(l1.stream(),l2.stream()).collect(Collectors.toList()));
}

public void remove() {
    for(Map.Entry<LocalDateTime, List<Task>> keyVal : tasks.entrySet()) {
        final List<Task> values = keyVal.getValue();
        if(isSomeCondition(keyVal) && tasks.remove(keyVal.getKey(), values)) {
            for (Task t : values) {
                //do task processing
            }
        }
    }
}

关键是地图中包含的列表永远不会被修改。如果没有先前的映射,merge(time, Collections.singletonList(task), … 操作甚至会存储单个任务的不可变列表。如果有以前的任务,合并函数(l1,l2) -&gt; Stream.concat(l1.stream(),l2.stream()).collect(Collectors.toList()) 将创建一个新列表,而不是修改现有的列表。当列表变得更大时,这可能会对性能产生影响,尤其是在争用情况下必须重复操作时,但这是不需要锁定或额外同步的代价。

remove 操作使用remove(key, value) 方法,该方法仅在映射的值仍与预期值匹配时才会成功。这依赖于这样一个事实,即我们的任何一种方法都不会修改地图中包含的列表,而是在合并时用新的列表实例替换它们。如果remove(key, value)成功,则可以处理列表;此时,它不再包含在地图中。请注意,在评估isSomeCondition(keyVal) 期间,该列表仍包含在地图中,因此,isSomeCondition(keyVal) 不得修改它,不过,我假设测试方法应该是这种情况无论如何,就像isSomeCondition。当然,在isSomeCondition 中评估列表也依赖于其他从不修改列表的方法。

【讨论】:

  • 此调用 keyVal.getValue() 是否可能具有过时的值?例如我们在 remove; 中获得 values = keyVal.getValue();下一个并行调用 add() ,它将再添加一个值并生成新列表;在这一刻,我们在值列表中有旧值,我们只对它们进行迭代,但删除所有列表
  • @Sergey: keyVal.getValue() 可能会收到一个过时的值,但在这种情况下,后续的remove(keyVal.getKey(), values) 将失败,因为值不匹配,因此不会采取任何措施。 remove() 循环仅处理值,如果 remove 操作成功,在这种情况下,后续的 add 操作将看到键不存在,而不是“添加更多值”,而是创建一个新列表。跨度>
猜你喜欢
  • 2018-03-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-04
  • 1970-01-01
  • 2019-04-24
  • 2018-08-08
  • 1970-01-01
  • 2018-12-26
相关资源
最近更新 更多