Search code examples
javamultithreadingconcurrencyexecutorservicesingle-threaded

How to manage single thread execution properly?


I have a process that I want to be triggered by different sources.

Let's say we have one case where we habe some other process (let's call it "manualStarter") under certain conditions wants to trigger this main process. The main process takes a while to complete, let's say 10 seconds to 10 minutes. In case the process is already in progress while the manualStarter is trying to start it, it should not be queued more than once. The second process to trigger the start of the main process could be a "timedStarter" which would trigger the process once in a while, but only if the process is not running, else it would not queue the process to be triggered, instead would try it again some time later.

Now I've tried implementing this kind of process manager by using the isAlive() and join(), but it seems isAlive() is not reliable at all, until it changes its state to alive, 100 threads of this thread might get started (and do sometimes). So seems I couldn't rely on that.

Then I tried using the SingleThreadExecutor service which is closer to what I'm looking for, it's not blocking anything and it only allows a single thread to execute the process, so that's good, however I still don't know how to check the status/lock it properly, or how else I can ensure that the queue for starting the thread doesn't become larger than 1. I read a bit that semaphores are often used for similar kinds of tasks, but I am not sure how I could use them in this scenario.

So how could I achieve what I want? Do I need to implement my own ThreadPoolExecutor? How can I do it? Is there any better way?


Solution

  • Just use a shared flag so the manual starter knows if the thread is running. For example:

    // Schedule this to run periodically via ScheduledExecutorService
    class ManualStarter {
        private final AtomicBoolen isRunning = new AtomicBoolean(false);
        private ExecutorService exec = Executors.newSingleThreadedExecutor();
    
        public void run() {
            if (!isRunning.getAndSet(true)) {
                // It wasn't running so this will start it
                exec.submit(new MainProcess(isRunning));
            }
        }
    }
    
    
    class MainProcess extends Runnable {
       private final AtomicBoolean isRunning;
    
       MainProcess(AtomicBoolean isRunning) { this.isRunning = isRunning; }
    
       @Override
       public void run() {
           // do whatever it does
           isRunning.set(false);
       }
    }
    

    Then somewhere you schedule the main thing to run periodically doing something like:

    ScheduledExectorService sched = Executors.newScheduledThreadPool(1);
    ManualStarter starter = new ManualStarter();
    // Every 10 seconds will check if MainProcess is running and will start
    // it if it's not
    sched..scheduleAtFixedRate(starter, 0, 10, SECONDS);