我想创建一个信号量,阻止某个方法一次执行超过1次。
如果任何其他线程请求访问,它应该等到信号量被释放:
private Map<String, Semaphore> map;
public void test() {
String hash; //prevent to run the long running method with the same hash concurrently
if (map.contains(hash)) {
map.get(hash).aquire(); //wait for release of the lock
callLongRunningMethod();
} else {
Semaphore s = new Semaphore(1);
map.put(hash, s);
callLongRunningMethod();
s.release(); //any number of registered threads should continue
map.remove(hash);
}
}
问题:如何只使用一个线程锁定信号量,但是将其释放,以便任何线程数可以在释放后立即继续?
一些澄清:
想象一下,长时间运行的方法是一种事务方法。查看数据库。如果未找到任何条目,则会发送大量XML请求并将其持久保存到db。也许可以进一步触发异步处理,因为这应该是&#34;初始提取&#34;的数据。然后从DB返回对象(在该方法中)。如果数据库条目已存在,则会直接返回实体。
现在,如果多个线程同时访问长时间运行的方法,则所有方法都会获取繁重的XML(流量,性能),并且所有方法都会尝试将相同的对象保存到数据库中(因为长时间运行的方法)是交易的)。导致例如非唯一的例外。加上所有这些都会触发可选的异步线程。
当除了一个线程之外的所有线程都被锁定时,只有第一个线程负责持久化该对象。然后,完成后,所有其他线程将检测到该条目已存在于DB中并且只是为该对象提供服务。
答案 0 :(得分:2)
据我了解,您不需要在此使用Semaphore
。相反,您应该使用ReentrantReadWriteLock
。此外,test
方法不是线程安全的。
下面的示例是使用RWL
的逻辑实现private ConcurrentMap<String, ReadWriteLock> map = null;
void test() {
String hash = null;
ReadWriteLock rwl = new ReentrantReadWriteLock(false);
ReadWriteLock lock = map.putIfAbsent(hash, rwl);
if (lock == null) {
lock = rwl;
}
if (lock.writeLock().tryLock()) {
try {
compute();
map.remove(hash);
} finally {
lock.writeLock().unlock();
}
} else {
lock.readLock().lock();
try {
compute();
} finally {
lock.readLock().unlock();
}
}
}
在此代码中,第一个成功的线程将获取WriteLock
,而其他Thread
将等待释放写锁定。释放WriteLock
所有等待释放的Thread
后,将同时进行。
答案 1 :(得分:0)
我认为你可以通过使用非常高的permit
数字(高于线程数,例如2000000)来做到这一点。
然后在应该专门运行许可证的acquire
函数(acquire(2000000)
)中,在其他线程中acquire
只运行一个许可证。
答案 2 :(得分:0)
据我了解您的需要,您希望能够确保任务首次由单个线程执行,然后您希望允许多个线程执行它,如果这样您需要依赖{{ 1}}作为下一个:
以下是CountDownLatch
:
CountDownLatch
答案 3 :(得分:0)
我认为最简单的方法是使用ExecutorService
和Future
:
class ContainingClass {
private final ConcurrentHashMap<String, Future<?>> pending =
new ConcurrentHashMap<>();
private final ExecutorService executor;
ContainingClass(ExecutorService executor) {
this.executor = executor;
}
void test(String hash) {
Future<?> future = pending.computeIfAbsent(
hash,
() -> executor.submit(() -> longRunningMethod()));
// Exception handling omitted for clarity.
try {
future.get(); // Block until LRM has finished.
} finally {
// Always remove: in case of exception, this allows
// the value to be computed again.
pending.values().remove(future);
}
}
}
从值中删除未来是线程安全的,因为computeIfAbsent
和remove
是原子的:computeIfAbsent
在remove
之前运行,在这种情况下现有的未来退回,并立即完成;或者在之后运行,并添加新的未来,从而导致对longRunningMethod
的新调用。
请注意,它会从pending.values()
中删除未来,而不是直接从pending
删除:请考虑以下示例:
如果通过密钥从地图中删除了未来,则线程2将删除线程3的未来,这与线程2的未来不同。
这也简化了longRunningMethod
,因为不再需要执行&#34;检查我是否需要做任何事情&#34;对于被阻塞的线程:Future.get()
已在阻塞线程中成功完成,足以表明不需要额外的工作。
答案 4 :(得分:-1)
我使用CountDownLatch
结束如下:
private final ConcurrentMap<String, CountDownLatch> map = new ConcurrentHashMap<>();
public void run() {
boolean active = false;
CountDownLatch count = null;
try {
if (map.containsKey(hash)) {
count = map.get(hash);
count.await(60, TimeUnit.SECONDS); //wait for release or timeout
} else {
count = new CountDownLatch(1);
map.put(hash, count); //block any threads with same hash
active = true;
}
return runLongRunningTask();
} finally {
if (active) {
count.countDown(); //release
map.remove(hash, count);
}
}
}