Calling a method only once with multiple threads

Viewed 2865

I have a web application which serves a lot of requests at the same time. In one of the app's API methods, I have a method - methodA(). In this method, I have a call to another method - doSomething(). I have a scenario in which I want the first call to methodA() will run the doSomething() method in a separate thread, but at this time, if another call to methodA() has been called, don't run the doSomething() method (because its still running by another thread) and just continue with the rest of methodA().

methodA() {
 .
 .
 doSomething() // In a new thread
 .
 .
}

I've considered using atomic boolean as a flag but I'm not sure if it's the best idea.

    private final AtomicBoolean isOn = new AtomicBoolean(false);
    methodA() {
        .
        .
        if (isOn.compareAndSet(false, true)) {
                     Runnable doSomethingRunnableTask = () -> { 
                         doSomething(); };
                     Thread t1 = new Thread(doSomethingRunnableTask);
                     t1.start();
                     isOn.set(false);
        } 

Thanks!

3 Answers

You could use a ReentrantLock. The lock will only allow one thread at a time, and its tryLock() method will return immediately with true or false depending on whether the lock was acquired.

ReentrantLock lock = new ReentrantLock();

methodA() {
    ...
    if (lock.tryLock()) {
        try {
            doSomething();
        } finally {
            lock.unlock();
        }
    }
    ...
}

If you want to execute doSomething() in another thread, and you do not want to block any of the calling threads, you could just go with something similar to what you initially thought about.

AtomicBoolean flag = new AtomicBoolean();

methodA() {
    ...
    if (flag.compareAndSet(false, true)) {
        // execute in another thread / executor
        new Thread(() -> {
            try {
                doSomething();
            } finally {
                // unlock within the executing thread
                // calling thread can continue immediately
                flag.set(false);
            }
        }).start();
    }
    ...
}

I think you could use ReentrantLock and it's tryLock method. From docs of ReentrantLock::tryLock

Acquires the lock only if it is not held by another thread at the time of invocation.

If the current thread already holds this lock then the hold count is incremented by one and the method returns true.

If the lock is held by another thread then this method will return immediately with the value false.

So you could create such lock in your service as a field so that Threads that invoke your methodA will share it and then :

public class MyService {
    private ReentrantLock reentrantLock = new ReentrantLock();

    public void methodA() {
        if(reentrantLock.tryLock()) {
            doSomething();
            reentrantLock.unlock();
        }
    }
}

EDIT : Here lock will be held by calling Thread and this thread will wait for the submitted task to finish and then unlock the lock :

public class MyService {
    private ReentrantLock reentrantLock = new ReentrantLock();
    
    private ExecutorService pool = Executors.newCachedThreadPool();
    
    public void methodA() {
        if(reentrantLock.tryLock()) {
            Future<?> submit = pool.submit(() -> doSomething()); // you can submit your invalidateCacheRunnableTask runnable here.
            try {
                submit.get();
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

Also remember that I used threadPool in this example so this pool needs to be closed appropriately.

Rather than using some kind of explicit lock, I would suggest you use a blocking queue. The advantage I see is that you will not need to spawn a thread repetitively if/when there is need. You will only need to spawn a thread once that will only handle all the doSomething.

Scenario:

When methodA is called, it puts the necessary information for your dedicated thread into a BlockingQueue and keeps going. A dedicated thread will poll the information from the BlockingQueue (blocking on an empty queue). When some information is received in the queue, it runs your doSomething method.

BlockingQueue<Info> queue;

methodA() {
    //...
    queue.add(info);
    // non-blocking, keeps going    
}


void dedicatedThread(){
    for(;;) {
        //Blocks until some work is put in the queue
        Info info = queue.poll(); 
        doSomething(info);
    }

}

Note: I assumed that type Info contains the necessary information for method doSomething. If however you do not need to share any information, I would suggest you use a Semaphore instead. In this case, methodA would put tickets in the semaphore and the dedicated thread would try to draw tickets, blocking until some tickets are received.

Related