import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DedupingQueue<E> implements QueueWrapper<E> {
private static final Logger LOGGER = LoggerFactory.getLogger(DedupingQueue.class);
private final Map<E, Future<E>> itemsBeingWorkedOn = new ConcurrentHashMap<>();
private final AsyncWorker<E> asyncWorker; //contains async method backed by a thread pool
public DedupingQueue(AsyncWorker<E> asyncWorker) {
this.asyncWorker = asyncWorker;
}
@Override
public Future<E> submit(E e) {
if (!itemsBeingWorkedOn.containsKey(e)) {
itemsBeingWorkedOn.put(e, asyncWorker.executeWorkAsync(e, this));
} else {
LOGGER.debug("Rejected [{}] as it's already being worked on", e);
}
return itemsBeingWorkedOn.get(e);
}
@Override
public void complete(E e) {
LOGGER.debug("Completed [{}]", e);
itemsBeingWorkedOn.remove(e);
}
@Override
public void rejectAndRetry(E e) {
itemsBeingWorkedOn.putIfAbsent(e, asyncWorker.executeWorkAsync(e, this));
}
}
我在推理上述代码的线程安全性方面遇到了一些困难。
我认为complete
和rejectAndretry
完全是线程安全的,因为地图是线程安全的。但是submit
呢,假设AsyncWorker
本身不是线程安全的?另外,如何在不使用synchronized
(使用内置ConcurrentHashMap保证)的情况下以最有效的方式使其线程安全??
答案 0 :(得分:2)
submit()
不是线程安全的,因为你的check-then-act序列不是原子序列。一个线程的特定键的状态可以在检查之后和调用之前由其他线程更改。使用ConcurrentHashMap中的原子方法(很可能是computeIfAbsent()
)来解决此问题。
if (!itemsBeingWorkedOn.containsKey(e)) {
itemsBeingWorkedOn.put(e, asyncWorker.executeWorkAsync(e, this));
}
如果AsyncWorker
不是线程安全的,那么整个类也不是线程安全的。但如果没有AsyncWorker
源代码,很难推理它。
答案 1 :(得分:2)
有compute()
方法,该函数接受键,当前值或null
如果没有当前映射,并返回值以保留在映射中,或者如果{{1返回。
null
请注意,您的队列未订购。如果您需要FIFO或LIFO订单,则应使用更合适的数据结构,例如@Override
public Future<E> submit(E e) {
return itemsBeingWorkedOn.compute(e, (k, v) -> {
if (v == null) {
return asyncWorker.executeWorkAsync(k, this);
} else {
LOGGER.debug("Rejected [{}] as it's already being worked on", k);
return v;
}
});
}
和LinkedHashMap
语句。