Search code examples
multithreadingspringexecutorserviceexecutor

Stopping a thread in Spring


In my Spring application, I start a thread like this.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.ContextStoppedEvent;
import org.springframework.stereotype.Component;

@Component
public class RunBackgroundServices implements ApplicationListener<ContextRefreshedEvent> {

    private final BackgroundServices backgroundServices;

    private ExecutorService executor;

    @Autowired
    public RunBackgroundServices(BackgroundServices backgroundServices) {
        this.backgroundServices= backgroundServices;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {

        executor = Executors.newSingleThreadExecutor();
        executor.submit(backgroundServices);
    }

    public void onApplicationEvent(ContextStoppedEvent event) {
        backgroundServices.close();
        executor.shutdownNow();
     }
}

Here is the thread BackGroundServices.

public class BackgroundServices extends Thread {

    @Autowired
    HL7Listener hL7Listener;

    @Autowired
    HISListener hISListener;

    @Autowired
    PollHIS pollHIS;

    @Autowired
    PollPatientPortal pollPatientPortal;

    private static final Logger logger = LoggerFactory.getLogger(BackgroundServices.class);

    public void run() {
        logger.debug("BackgroundServices :: run");
        try {
            hL7Listener.start();
        } catch (InterruptedException e) {
            logger.error(e.getStackTrace().toString());
        }

        try { 
            hISListener.start();
        } catch (InterruptedException e) {
            logger.error(e.getStackTrace().toString());
        }

        while (true) {
            pollHIS.start();
            pollPatientPortal.start();
        }
    }

    public void close(){
        hL7Listener.stop();
        hISListener.stop();
    }
}

But when I stop the server, the thread continues running in the background. I have no control over the executor, is there a way to stop the thread?


Solution

  • Calling shutdown or shutdownNow on an ExecutorService will not stop the threads that are currently executing a task. shutdownNow will wait for all currently executing tasks to finish. Stopping threads forcibly is a very bad thing, and no well-behaved Java code will do that.

    If you want to stop your thread, then you need to pass a signal of some kind to the thread, and your code then needs to read that signal. ExecutorService#shutdownNow does half of this for you - it sends an interrupt signal to the currently executing threads. Your code has the beginnings of interrupt handling, but it's not properly implemented.

    When you get an InterruptedException, you can't just log it and move on - that exception is telling you that your thread has been interrupted, and it needs to finish what it's doing and exit (see Java theory and practice: Dealing with InterruptedException - a superb article well worth reading for novices and experts alike). Rather than logging an error when you get InterruptedException, your code needs to exit the method (e.g. using return).

    In your while loop, you then need to check to see if the thread has been interrupted, and exit if it has. So your run method now becomes something like this:

    public void run() {
        logger.debug("BackgroundServices :: run");
        try {
            hL7Listener.start();
            hISListener.start();
        } catch (InterruptedException e) {
            logger.error(e.getStackTrace().toString());
            return;
        }
    
        while (true) {
            if (Thread.currentThread.isInterrupted()) {
                logger.debug("Thread interrupted, exiting");
                return;
            }
            pollHIS.start();
            pollPatientPortal.start();
        }
    }
    

    One last thing - BackgroundServices shouldn't extend Thread, it should just implement Runnable. The ExecutorService is treating it as a Runnable because Thread happens to implement that interface, but ExecutorService creates and manages its own threads. Extending Thread yourself is just going to result in confusion.