简体   繁体   中英

Java ExecutorService:- Notify a thread to wake up when an event occurs

I have a Manager class to which multiple threads register themselves (used UUID to generate unique identifiers per requests), gives payload to process and get their corresponding responses from the manager. I am using java.util.concurrent.ExecutorService to launch multiple threads. Here is an implementation for testing my Manager functionality-

public class ManagerTest {
    public static void main(String[] args) {
        try {
            Manager myManager = new Manager();
            // Start listening to the messages from different threads
            myManager.consumeMessages();
            int num_threads = Integer.parseInt(args[0]);
            ExecutorService executor = Executors.newFixedThreadPool(num_threads);

            for (int i = 0; i < num_threads; i++) {
                // class implementation is given below
                Runnable worker = new MyRunnable(myManager);
                executor.execute(worker);
            }
            executor.shutdown();
            // Wait until all threads are finish
            while (!executor.isTerminated()) {

            }
            System.out.println("\nFinished all threads");

            myManager.closeConnection();

        } catch (IOException | TimeoutException e) {
            e.printStackTrace();
        }
    }
}

Here is the implementation of MyRunnable class

class MyRunnable implements Runnable {
    private Manager managerObj;
    public MyRunnable(Manager managerObj) {
        this.managerObj = managerObj;
    }

    @Override
    public void run() {     
        try {
            Random rand = new Random();
            int  n = rand.nextInt(35);
            String requestId = UUID.randomUUID().toString();
            managerObj.registerRequest(requestId, n);
            managerObj.publishMessage(requestId);
            // Want to avoid this while loop
            while( ! managerObj.getRequestStatus(requestId)){

            }
            int response = managerObj.getRequestResponse(requestId);
            // do something else
            managerObj.unregisterRequest(requestId);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The manager will process requests and depending on the payload the response of a request can take varying amount of time. Whenever manager gets the response it sets the requests status to true by calling this function setRequestStatus(requestId) . After this the thread will exit from the while loop and continues its execution.

The code if working fine, but the thread is doing too much work then needed by continuously looping over the while loop until the condition is met.

Is their a way to make a thread sleep after sending the requests to the manager, and manager signals this thread to wake up when its response is ready.

Pardon me if this sounds too simple to someone, I am a newbie to java and java-threading interface.

That's fine, we are used to simple questions, and your question is actually well written and has a reasonable problem to solve, we see a lot worse here every day, like people not knowing what they want, how to ask for it, etc.

So, what you are doing is a busy-spin-loop, and it is a very bad thing to do, because a) it consumes a full CPU core per thread, and b) it actually keeps the CPU busy, which means that it is stealing processing time from other threads that might have useful work to do.

There is a number of ways you can solve this, I will list them from worst to best.

  • The simplest way to improve your code is to invoke the java.lang.Thread.sleep(long millis) method, passing it 0 as a parameter. This is also known as a "yield" operation, and it essentially means "if there are any other threads that have some useful work to do, let them run, and return back to me once they are done." This is only marginally better than busy-spin-looping because it will still consume 100% CPU. The benefit is that it will only consume CPU while other threads do not have anything to do, so at least it will not slow other things down.

  • The next best, but still not very smart, way to improve your code is by invoking the java.lang.Thread.sleep(long millis) method passing it 1 as a parameter. This is known as a "pass" operation, and it essentially means "release the remainder of my time-slice to any other threads that might have some useful work to do". In other words, the remainder of the time-slice is forfeited, even if no useful work needs to be done in the entire system. This will bring CPU consumption down to almost zero. The disadvantages are that a) the CPU consumption will actually be slightly above zero, b) the machine will not be able to go to some low-power sleep mode, and c) your worker thread will be slightly less responsive: it will pick up work to do only on a timeslice boundary.

  • The best way to solve your problem is by using the synchronization mechanism built into java, as explained in this answer: https://stackoverflow.com/a/5999146/773113 This will not only consume zero CPU, but it will even allow the machine to go into a low power mode while waiting.

  • To solve your problem for the most general case, where you don't want to just wait until a condition, but to actually also pass information about the work that has been done or work which is to be done, you would use a BlockingQueue . ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html ) The blocking queue works using the java built-in synchronization mechanism and allows one thread to pass information to another.

There's already a great answer by @MikeNakis, but I feel inclined to offer another option as well.

This option however involves changing the Manager API to return a Future .

The changes to Manager I would propose are these :

  • drop the getRequestStatus() method
  • make getRequestResponse() return Future<Integer>

with these changes in place MyRunnable 's run() method can change to :

public void run() {     
    try {
        Random rand = new Random();
        int  n = rand.nextInt(35);
        String requestId = UUID.randomUUID().toString();
        managerObj.registerRequest(requestId, n);
        managerObj.publishMessage(requestId);

        // Future.get() blocks and waits for the result without consuming CPU
        int response = managerObj.getRequestResponse(requestId).get();
        // do something else
        managerObj.unregisterRequest(requestId);

    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

The easiest way to implement the Future<> will likely be using Java's java.util.concurrent.FutureTask .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM