mercury0114
mercury0114

Reputation: 1459

Java: How to put thread back to sleep INDEFINITELY after interrupt?

I am writing Java software, that has a single thread, which listens to external buttons being pressed. If the button is pressed, the thread informs other threads, but otherwise it just sleeps.

My model is to use interrupt-driven design. Ideally I would like to make a thread sleep as long as no button is pressed. When the button is pressed I would like the thread to do some work and go back to sleep.

Could anyone confirm / correct the following implementation?

// This is a code that interrupt-driven thread will execute
public void run() {
    while (true) {
        try {
            Thread.sleep(1000); // Sleeps only for 1s. How to sleep indefinitely?
        } catch (InterruptedException exception) {
            process(exception); // Doing some work
            // then going back to sleep using the while loop
        }
    }
}

Also, after each button click in the terminal I get a message

I/O exception raised from stop()

What does this message mean (i.e why is it printed if I catch the exception)? Can I avoid the terminal to print it?

Upvotes: 1

Views: 266

Answers (2)

OldCurmudgeon
OldCurmudgeon

Reputation: 65869

It is generally considered a code smell if you use exceptions to control your program flow.

The correct solution to this problem is to use a BlockingQueue of events that the event handler reads from. This is commonly called a producer/consumer.

public class TwoThreads {

    public static void main(String args[]) throws InterruptedException {
        System.out.println("TwoThreads:Test");
        new TwoThreads().test();
    }

    // The end of the list.
    private static final Integer End = -1;

    static class Producer implements Runnable {

        final BlockingQueue<Integer> queue;

        public Producer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            try {
                for (int i = 0; i < 1000; i++) {
                    queue.add(i);
                    Thread.sleep(1);
                }
                // Finish the queue.
                queue.add(End);
            } catch (InterruptedException ex) {
                // Just exit.
            }
        }

    }

    static class Consumer implements Runnable {

        final BlockingQueue<Integer> queue;

        public Consumer(BlockingQueue<Integer> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            boolean ended = false;
            while (!ended) {
                try {
                    Integer i = queue.take();
                    ended = i == End;
                    System.out.println(i);
                } catch (InterruptedException ex) {
                    ended = true;
                }
            }
        }

    }

    public void test() throws InterruptedException {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
        Thread pt = new Thread(new Producer(queue));
        Thread ct = new Thread(new Consumer(queue));
        // Start it all going.
        pt.start();
        ct.start();
        // Wait for it to finish.
        pt.join();
        ct.join();
    }

}

Don't let yourself be confused by how much code this is - most of it is just wrapping. The core functionality is:

At start - create a BlockingQueue and share it between the two threads.

    BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();
    Thread pt = new Thread(new Producer(queue));
    Thread ct = new Thread(new Consumer(queue));

When an event happens, post to the queue.

                queue.add(i);

The event handler feeds off the queue.

        while (!ended) {
            try {
                Integer i = queue.take();

Note that take here will block until an event is posted or an interrupt occurrs.

Upvotes: 7

Peter Lawrey
Peter Lawrey

Reputation: 533820

You can use

Thread.sleep(Long.MAX_VALUE); // more than the life of your computer

or

synchronized(this) {
      wait();
}

or this wake on interrupt but doesn't throw an exception

LockSupport.park();

However a more elegant solution is likely to be to use an ExecutorService is designed to be a sleeping thread pool which wakes when you give it work to do.

ExecutorsService executor = Executors.newSingleThreadExecutor();

// when you want it to do something
executor.submit(this::process);

Note: you should consider how you want to handle exceptions. In the example in your question, an exception or error will kill the thread and it will stop running. In my example it won't kill the thread pool but the actual exception could be lost. For this reason I suggest you write it like this.

executor.submit(() -> {
    try {
        process();
    } catch(Throwable t) {
        LOGGER.warning(t);
    }
});

Note: instead of just calling process and it having to figure out what you want to do you can write it like this.

doSomething(string, number, pojo);

That way you can see what data you expect the background thread to work on.


For comparison, here is the TwoThread example using the current thread as a producer and an Executor Service.

public class TwoThreadsJava5 {

    public static void main(String args[]) throws InterruptedException {
        System.out.println("TwoThreads:Test - Java 5.0 style");

        ExecutorService executor = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 1000; i++) {
            final int finalI = i;
            executor.submit(() -> {
                try {
                    System.out.println(finalI);
                } catch (Throwable t) {
                    t.printStackTrace();
                }
            });
        }
        executor.shutdown();
        executor.awaitTermination(1, TimeUnit.MINUTES);
    }
}

And in Java 8 you could write

public class TwoThreadsJava8 {
    public static void main(String args[]) throws InterruptedException {
        System.out.println("TwoThreads:Test - Java 8 style");

        IntStream.range(0, 1000)
                .parallel()
                .forEach(System.out::println);
    }
}

Upvotes: 3

Related Questions