There are two basic families of architectures for using threads: dedicated thread models and worker thread models. In a dedicated thread model, each thread does just one thing. In a worker thread model, each thread is a general purpose engine which pulls tasks from one or more task queues. We will discuss dedicated thread models in a separate section. Note that you can freely combine dedicated and worker threads according to your needs.
Worker threads are a good starting assumption for a number of reasons, we will explain shortly. But first, let’s see the pattern in its simplest form:
public class Worker implements Runnable { private static final Vector q = new Vector(); public static void queue(Runnable r) { synchronized (q) { q.addElement(r); q.notify(); } } public void run() { Runnable r; while (true) { try { synchronized (q) { if (q.isEmpty()) { q.wait(); } r = (Runnable) q.firstElement(); q.removeElementAt(0); } r.run(); } catch (Exception e) { } } } }
Notice that we can have several Worker
s, each
feeding off on the single common queue. We make no assumptions about
which Worker
does what, we simply create perhaps
four Worker
s and let them sort things out by working
through the task backlog as quickly as possible.
There are several advantages to this over a dedicated thread model:
It takes you less time and less code than dedicated threads, and less code is more reliable and faster. You do not need to re-create the (error prone) multi-threading glue code mechanisms for keeping the engine running at full speed.
It performs extremely well to have all engines pulling full speed on whatever tasks currently need to be done. You can scale up and down the number of workers according to your needs when sport tuning the application.
You end up needing fewer threads which saves resources and performs well.
There are several disadvantages of a basic worker model which may necessitate adding code to work past, or adapting your coding style and algorithms accordingly:
Execution order is not strictly guaranteed. The tasks will
start in roughly the order in which they are added to the queue, but
if you need a strict execution order, you should add one Runnable
to the queue which executes the several tasks in sequence on a single
thread.
If all threads are busy on one type of task, other tasks of
different types will not proceed. This is less of a problem than it
may sound. If you find this as a problem, the solution is to keep
the size of tasks you place on the queue relatively small, and let
them proceed with getting the job done. Another approach is to bundle
all the troublesome tasks into a single Runnable
so
that only one Worker
completes the entire batch while
other Worker
s are free to handle other tasks.
Tasks which have a tendency to hang or take a long time, such
as media players and gaming loops, will block a Worker
until they complete. In these cases, it may be clearer
programming to create a dedicated thread rather than lock up a Worker
for an extended period of time.