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 Workers, each feeding off on the single common queue. We make no assumptions about which Worker does what, we simply create perhaps four Workers 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 Workers 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.