|
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
Kindle Fire: what do you think?
Java 7: what is the most interesting aspect?
Do you have a Kindle? Sign up to the newsletter
Follow @BitterCoffey on Twitter
RECOMMEND THIS SITE TO FRIENDS/COLLEAGUES:
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.
Did this article solve your problem? If not, you can now
post a comment or question
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 2011. All rights reserved.
|