membersound membersound - 7 months ago 26
Java Question

How to release a semaphore and let any threads continue?

I want to create a semaphore that prevents a certain method to be executed more than 1x at a time.

If any other thread requests access, it should wait until the semaphore is released:

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


Question: how can I lock the semaphore with just one thread, but release it so that any number of threads can continue as soon as released?

Some clarifications:

Imagine the long running method is a transactional method. Looks into the database. If no entry is found, a heavy XML request is send and persisted to db. Also maybe further async processed might be triggered as this is supposed to be the "initial fetch" of the data. Then return the object from DB (within that method). If the DB entry had existed, it would directly return the entity.

Now if multiple threads access the long running method at the same time, all methods would fetch the heavy XML (traffic, performance), and all of them would try to persist the same object into the DB (because the long running method is transactional). Causing eg non-unique exceptions. Plus all of them triggering the optional async threads.

When all but one thread is locked, only the first is responsible for persisting the object. Then, when finished, all other threads will detect that the entry already exists in DB and just serve that object.

Answer

As far as I understand, you don't need to use Semaphore here. Instead, you should use ReentrantReadWriteLock. Additionally, the test method is not thread safe.

The sample below is the implementation of your logic using 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();
        }
    }
}

In this code, the first successful thread would acquire WriteLock while other Threads would wait for release of write lock. After release of a WriteLock all Threads waiting for release would proceed concurrently.