如何释放信号量并让任何线程继续?

时间:2016-04-22 08:58:43

标签: java multithreading semaphore

我想创建一个信号量,阻止某个方法一次执行超过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中并且只是为该对象提供服务。

5 个答案:

答案 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)

我认为最简单的方法是使用ExecutorServiceFuture

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);
    }
  }
}

Ideone Demo

从值中删除未来是线程安全的,因为computeIfAbsentremove是原子的:computeIfAbsentremove之前运行,在这种情况下现有的未来退回,并立即完成;或者在之后运行,并添加新的未来,从而导致对longRunningMethod的新调用。

请注意,它会从pending.values()中删除未来,而不是直接从pending删除:请考虑以下示例:

  • 线程1和线程2同时运行
  • 线程1完成,并删除该值。
  • 线程3正在运行,为地图添加了新的未来
  • 线程2完成,并尝试删除该值。

如果通过密钥从地图中删除了未来,则线程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);
            }
        }
}