Java threading introduction  Thread-safety  Thread methods  Interruption  Thread scheduling  Context switching  Thread priorities  sleep()  yield()  Deadlock  Threading with Swing  invokeLater()  Thread pools  CoundDownLatch  ThreadPoolExecutor  CyclicBarrier

Controlling the queue with ThreadPoolExecutor

The previous page showed a skeleton of a simple server with ThreadPoolExecutor. We used a common paradigm, in which one thread continually sits waiting to accept connections; these connections are then each farmed off to be executed by the next available thread. Now, one problem that can occur is if we get a large volume of incoming connections so that the available threads can't proess them fast enough. In this case, the connections waiting to be processed will be queued. But we haven't put any bounds on the queue, so that in the worst case, they will just continue to "pile up". If connections aren't being processed fast enough because the server is overloaded or "has a problem", then we're not going to help matters by piling up an endless number of connections that the server doesn't have a realistic chance of processing. At some point, we need to accept that "the server is busy" and drop further connections until things have calmed down.

To achieve this goal, we need to:

  • use a queue with a some maximum capacity;
  • handle rejected execution: add a piece of code to deal with what happens when an incoming job won't fit in the queue.

Specifying a queue with maximum capacity

In our initial example, for convenience, we just used the Executors helper class to construct a thread pool with default options. However, if we constract a ThreadPoolExecutor object directly via its constructor, we can specify various additional parameters including the implementation of BlockingQueue that we wish to use as the job queue. In this case, we can use an ArrayBlockingQueue or LinkedBlockingQueue with a maximum capacity. The queue is declared to take objects of type Runnable, since this is what the thread pool deals with:

BlockingQueue q = new ArrayBlockingQueue(20);
ThreadPoolExecutor ex = new ThreadPoolExecutor(4, 10, 20, TimeUnit.SECONDS, q);

Note a side effect of specifying our own queue is that we must specify the maximum number of threads (10 in this case) and the time-to-live of idle threads (20 seconds in this case). As the number of simultaneous connections grows, the thread pool will automatically expand the number of threads up to this maximum. When the number of connections (and hence threads needed) decreases, the thread pool will "kill" each spare thread after it has been sitting idle for 20 seconds, until we're down to our "core" size of 4 threads (the first parameter).

If you specify your own job queue, be careful not to post jobs "manually" to the queue (using the regular queue methods). If you do so, the job will not be picked up by the thread pool. Always use ThreadPoolExecutor.execute() even though it's "your own queue".

Rejected execution handlers and RejectedExecutionException

With an upper bound on our queue size, the other issue we need to deal with is what happens if a job isn't executed because the queue is full. In this case, we'll be left with a "dangling" socket that we should close as soon as possible. By default, we can handle the full queue situation by catching RejectedExecutionException:

while (!shutDownRequested()) {
  Socket s = null;
  try {
    s = ss.accept();
    exec.execute(new ConnectionRunnable(s));
  } catch (RejectedExecutionException rej) {
    try { s.close(); } catch (Exception ignore) {}
  } catch (Exception e) {
    // ... log
  }
}

Another way to handle closing the socket is to pass a RejectedExecutionHandler into the constructor of our ThreadPoolExecutor. RejectedExecutionHandler is an interface that specifies one method that we must define:

public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
  ...
}

Then, instead of throwing an exception, if the connection job won't fit on the queue, the ThreadPoolExecutor will call our rejectedExecution() method instead. Whether you catch the exception or define a separate handler essentially depends on which makes your design easier— for example, you could have a single rejection handler shared by multiple executors.


 Java threading articles  Java threading and concurrency  Java profiling  Java performance graph index

Unless otherwise stated, the Java programming articles and tutorials on this site are written by Neil Coffey. Suggestions are always welcome if you wish to suggest topics for Java tutorials or programming articles, or if you simply have a programming question that you would like to see answered on this site. Most topics will be considered. But in particular, the site aims to provide tutorials and information on topics that aren't well covered elsewhere, or on Java performance information that is poorly described or understood. Suggestions may be made via the Javamex blog (see the site's front page for details).
Copyright © Javamex UK 2016. All rights reserved.