javamultithreadingalgorithmconcurrencyreentrantlock

How to handle multiple threads efficiently / only perform a load operation once for all queued up load calls?


I'm attempting to write java code that executes a load operation as described below.

Initial Call (Thread One): Initiates a loading process and immediately returns.

Second Call (Thread Two):

If Thread One is in the midst of loading, it waits. Once Thread One finishes, Thread Two starts its own load. After its load is done, it returns.

Subsequent Calls (Thread Three and beyond): If Thread One is loading, it waits. Does not queue behind both Thread One and Thread Two. Instead, it leverages the load process initiated by Thread Two. Returns in tandem with Thread Two once the loading is completed.

The load method is called from an api-controller method.

My current implementation is not good because the same thread is handling the flush for the next threads, while it should have returned. If the method is invoked multiple times and the isLoadPending flag is set, the same thread continually handles the load, potentially without ever releasing. Ideally, after waiting for a prior load to finish, a thread should only need to flush at most one more time.

private final ReentrantLock loadLock = new ReentrantLock();
private final AtomicBoolean isLoadPending = new AtomicBoolean(false);

public void loadAll() {
    if (loadLock.tryLock()) {
        try {
            DbLoadService.load();
            while (isLoadPending.compareAndSet(true, false)) {
                DbLoadService.load();
            }
        } finally {
            loadLock.unlock();
        }
    } else {
        isLoadPending.set(true);
        loadLock.lock();
        loadLock.unlock();
    }
}

Any suggestions?


Solution

  • I think, you want something like this:

    final Phaser loadPhaser = new Phaser() {
        protected boolean onAdvance(int phase, int registeredParties) {
            DbLoadService.load();
            return false;
        }
    };
    
    public void loadAll() {
        int myPhase = loadPhaser.register();
        loadPhaser.arriveAndDeregister();
        loadPhaser.awaitAdvance(myPhase);
    }
    

    In this specific use case, the threads belonging to a particular phase are the threads entering loadAll() at the same time or while a previous DbLoadService.load() is in progress. Since this is different for each phase, they have to register and unregister each time.

    All threads calling register() while a previous DbLoadService.load() is in progress will wait for its completion, then all of them will call arriveAndDeregister() and the last one will perform the new DbLoadService.load() operation. The others will wait for the completion of this new DbLoadService.load() operation in awaitAdvance.

    New threads arriving at register() are clearly distinct from the threads waiting at awaitAdvance at the same time—they belong to a different phase. In fact, when this overlapping happens, it is impossible for the thread performing the current load() to become part of the next phase, so it’s impossible for a thread to get stuck in repeatedly performing the load operation.