假设我有一个Set
和另一个Queue
。我想查看set
contains(Element)
,如果不是add(element)
,请queue
。我想原子地完成这两个步骤。
一种显而易见的方法是使用synchronized
块或Lock.lock()/unlock()
方法。在线程争用下,这些将导致上下文切换。是否有任何简单的设计策略以非阻塞的方式实现这一目标?可能正在使用一些原子构造?
答案 0 :(得分:1)
我不认为你可以依赖任何机制,除了你自己指出的机制,只是因为你在两个结构上运作。
对一个数据结构的并发/原子操作提供了不错的支持(如ConcurrentHashMap中的“put if not exists”),但是对于一系列操作,你会遇到锁定或同步块。
答案 1 :(得分:1)
由于争用案例是相关案例,您应该查看“自旋锁”。它们并没有放弃CPU,而是旋转一个标志,希望标志很快就会释放。
但请注意,真正的自旋锁在Java中很少有用,因为正常的Lock
非常好。请参阅this blog,其中某人首先在Java中实现了一个自旋锁,但发现经过一些修正后(即在完成测试之后),旋转锁与标准内容相同。
答案 2 :(得分:1)
对于某些操作,您可以使用所谓的“安全序列”,其中并发操作可能会重叠而不会发生冲突。例如,您可以在不需要同步的情况下将成员添加到集合中(理论上),因为同时添加相同成员的两个线程在概念上不会相互冲突。
但是查询一个对象然后有条件地操作不同的对象是一个复杂得多的场景。 如果你的序列是查询集合,然后有条件地将成员插入集合并进入队列,查询和第一次插入可以替换为“比较和交换”操作同步而不停顿(除了可能在内存访问级别),然后可以根据第一个操作的成功将成员插入队列,只需要同步队列插入本身。但是,此序列会留下另一个线程可能使插入失败但仍未在队列中找到该成员的情况。
答案 3 :(得分:1)
您可以使用java.util.concurrent.ConcurrentHashMap
来获取所需的语义。他们有putIfAbsent
进行原子插入。然后,您基本上尝试向地图添加元素,如果成功,您就知道执行插入的线程是唯一拥有的线程,然后您可以安全地将该项目放入队列中。另一个重要的一点是,ConcurrentMap上的操作确保了“发生之前”的语义。
ConcurrentMap<Element,Boolean> set = new ConcurrentHashMap<Element,Boolean>();
Queue<Element> queue = ...;
void maybeAddToQueue(Element e) {
if (set.putIfAbsent(e, true) == null) {
queue.offer(e);
}
}
注意,地图的实际值类型(布尔值)在这里并不重要。