Threads are a very important aspect of Java, but creating large numbers of threads can negatively impact program performance. Discover the advantages of thread pools, which allow you to limit the total number of threads running assigned tasks to each of the threads.
This article shows you how to create a thread pool in Java. A complete thread pool is included with this article. This thread pool is extensible enough to be added to your own programs to add thread pool functionality. This article will focus on how the thread pool was created and how it is used.
A thread allows Java to perform more than one task at a time. In much the same way as multitasking allows your computer to run more than one program at a time, multithreading allows your program to run more than one task at a time. Depending on the type of program, multithreading can significantly increase the performance of a program.
When to Use Multithreading
There are two primary cases in which multithreading can increase performance. The first is when the program is run on a multiprocessor computer, which will do little for your program if it is not multithreaded. A multiprocessor computer works by using the multiple processors to handle threads simultaneously. If your program uses only the one thread that all programs begin with, multiple processors will do your program little good because the computer has no way to divide your program among the processors.
The second type of program that greatly benefits from multithreading is a program that spends a great deal of time waiting for outside events. One example of this is a Web crawler, which must visit a Web page and then visit all of the links on that page. When crawling a large site, your program must examine a considerable amount of pages. Requesting a Web page can take several seconds—even on a broadband connection. This is a considerable amount of time for a computer to wait for each Web page. If the crawler has a considerable number of pages to visit, these mere seconds can really add up.
It would be much better for the crawler to request a large number of Web pages and then wait for each of these pages at the same time. For example, the program may use 10 different threads to request 10 different Web pages. The program is now waiting for 10 pages, rather than just one. Because the time spent waiting for the page is idle, the program can be waiting for a large number of pages before performance degrades. Also, because the pages are being waited for in parallel, the entire process takes only a fraction of the time that it would when the pages were waited on individually.
Why a Thread Pool?
When programming the crawler in the previous section, a problem that would soon present itself is the number of threads to use. A crawler may have to visit tens of thousands of pages, and you certainly do not want to create tens of thousands of threads because each thread imposes a certain amount of overhead. If the number of threads grows too large, the computer will be spending all of its time switching between threads, rather than just executing them.
To solve this problem, you must create a thread pool. The thread pool is given some fixed number of threads to use. The thread pool will assign its tasks to each of these threads. As the threads finish with old tasks, new ones are assigned. This causes the program to use a fixed number of threads, not to be continually creating new threads.
Unfortunately, there is no thread-pooling feature built into Java; thread pooling must be implemented by the programmer. I will now show you my thread pool.
Implementing the Thread Pool
The main class file that makes up my thread pool is the ThreadPool.java source file (this source file can be seen in Listing 1 at the end of the article). The listing is well-documented, and should allow you to understand the details of the program. I will now explain the general flow of the program.
The thread pool contains an array of WorkerThread objects. These objects are the individual threads that make up the pool. The WorkerThread objects will start and stop as work arrives for them. If there is more work than there are WorkerThreads, the work will backlog until WorkerThreads free up.
When you first create a new ThreadPool object the WorkerThreads are initially paused, waiting for work. You assign work to the ThreadPool using the assign method. Any class that implements the Runnable interface can be passed to the assign method. The assign method places the object into assignments array, in which it is picked up by a waiting thread.
Are We Done Yet?
Knowing when the thread pool has completed its task can be complex. There are several things that must be checked to determine whether the thread pool is completely done. First, the assignments array must be empty. However, an empty assignments array does not mean that the thread pool is done. There may still be threads inside of the pool that are executing tasks that were previously assigned.
To determine whether the thread pool is done or not, I have provided the Done class (shown in Listing 2), which is used internally by the thread pool. All that you have to do to make use of it is assign your tasks to the ThreadPool and call the complete method to wait for the ThreadPool to complete. The Done class is used to determine when no threads are still running.
The Done class has two methods that are called by the worker threads to track their progress. When a worker thread begins, it calls the Done class' workerBegin method. Similarly, when a worker thread completes, it calls the Done class's workerEnd method. These two methods allow the Done class to determine when no thread is currently running.
Most likely, you will not directly interact with the Done class. You will simply assign your tasks to the ThreadPool and wait for the ThreadPool to complete. In the next section, I will show you an example of how the whole thread pool fits together.
Thread Pool Example
I will now show you an example of how to use the thread pool. First, you must crate a class that implements the Runnable interface and contains a run method that will execute your task. An example class that does this is shown in Listing 3.
This is a very simple task that counts from zero to 100 percent. This count takes a random amount of time. The countdown is implemented in the run method of the TestWorkerThread. The main program, which is shown in Listing 4, will create 50 of these TestWorkerThread classes and assign them to the thread pool. The ThreadPool will execute up to 10 tasks at a time. After the program is running, you will see 10 of the threads counting their percent up to 100. Once one of these ten completes, another is chosen from the waiting assignments.
Conclusions
As you can see, a thread pool is a useful way to limit the amount of threads that a Java program will be using. This article presented a complete thread pool that can be used with Java. This thread pool can easily become the starting point for any application that you create, which requires a thread pool.
Listing 1—The ThreadPool Class
package com.heaton.threads;
import java.util.*;
/**
* Java Thread Pool
*
* This is a thread pool that for Java, it is
* simple to use and gets the job done. This program and
* all supporting files are distributed under the Limited
* GNU Public License (LGPL, http://www.gnu.org).
*
* This is the main class for the thread pool. You should
* create an instance of this class and assign tasks to it.
*
* For more information visit http://www.jeffheaton.com.
*
* @author Jeff Heaton (http://www.jeffheaton.com)
* @version 1.0
*/
public class ThreadPool {
/**
* The threads in the pool.
*/
protected Thread threads[] = null;
/**
* The backlog of assignments, which are waiting
* for the thread pool.
*/
Collection assignments = new ArrayList(3);
/**
* A Done object that is used to track when the
* thread pool is done, that is has no more work
* to perform.
*/
protected Done done = new Done();
/**
* The constructor.
*
* @param size How many threads in the thread pool.
*/
public ThreadPool(int size)
{
threads = new WorkerThread[size];
for (int i=0;i<threads.length;i++) {
threads[i] = new WorkerThread(this);
threads[i].start();
}
}
/**
* Add a task to the thread pool. Any class
* which implements the Runnable interface
* may be assienged. When this task runs, its
* run method will be called.
*
* @param r An object that implements the Runnable interface
*/
public synchronized void assign(Runnable r)
{
done.workerBegin();
assignments.add(r);
notify();
}
/**
* Get a new work assignment.
*
* @return A new assignment
*/
public synchronized Runnable getAssignment()
{
try {
while ( !assignments.iterator().hasNext() )
wait();
Runnable r = (Runnable)assignments.iterator().next();
assignments.remove(r);
return r;
} catch (InterruptedException e) {
done.workerEnd();
return null;
}
}
/**
* Called to block the current thread until
* the thread pool has no more work.
*/
public void complete()
{
done.waitBegin();
done.waitDone();
}
protected void finalize()
{
done.reset();
for (int i=0;i<threads.length;i++) {
threads[i].interrupt();
done.workerBegin();
threads[i].destroy();
}
done.waitDone();
}
}
/**
* The worker threads that make up the thread pool.
*
* @author Jeff Heaton
* @version 1.0
*/
class WorkerThread extends Thread {
/**
* True if this thread is currently processing.
*/
public boolean busy;
/**
* The thread pool that this object belongs to.
*/
public ThreadPool owner;
/**
* The constructor.
*
* @param o the thread pool
*/
WorkerThread(ThreadPool o)
{
owner = o;
}
/**
* Scan for and execute tasks.
*/
public void run()
{
Runnable target = null;
do {
target = owner.getAssignment();
if (target!=null) {
target.run();
owner.done.workerEnd();
}
} while (target!=null);
}
}
Listing 2—The Done Class
package com.heaton.threads;
/**
*
* This is a thread pool for Java, it is
* simple to use and gets the job done. This program and
* all supporting files are distributed under the Limited
* GNU Public License (LGPL, http://www.gnu.org).
*
* This is a very simple object that
* allows the TheadPool to determine when
* it is done. This object implements
* a simple lock that the ThreadPool class
* can wait on to determine completion.
* Done is defined as the ThreadPool having
* no more work to complete.
*
* Copyright 2001 by Jeff Heaton
*
* @author Jeff Heaton (http://www.jeffheaton.com)
* @version 1.0
*/
public class Done {
/**
* The number of Worker object
* threads that are currently working
* on something.
*/
private int _activeThreads = 0;
/**
* This boolean keeps track of if
* the very first thread has started
* or not. This prevents this object
* from falsely reporting that the ThreadPool
* is done, just because the first thread
* has not yet started.
*/
private boolean _started = false;
/**
* This method can be called to block
* the current thread until the ThreadPool
* is done.
*/
synchronized public void waitDone()
{
try {
while ( _activeThreads>0 ) {
wait();
}
} catch ( InterruptedException e ) {
}
}
/**
* Called to wait for the first thread to
* start. Once this method returns the
* process has begun.
*/
synchronized public void waitBegin()
{
try {
while ( !_started ) {
wait();
}
} catch ( InterruptedException e ) {
}
}
/**
* Called by a Worker object
* to indicate that it has begun
* working on a workload.
*/
synchronized public void workerBegin()
{
_activeThreads++;
_started = true;
notify();
}
/**
* Called by a Worker object to
* indicate that it has completed a
* workload.
*/
synchronized public void workerEnd()
{
_activeThreads--;
notify();
}
/**
* Called to reset this object to
* its initial state.
*/
synchronized public void reset()
{
_activeThreads = 0;
}
}
Listing 3—The Example Worker Thread
import com.heaton.threads.*;
/**
* This class shows an example worker thread that can
* be used with the thread pool. It demonstrates the main
* points that should be included in any worker thread. Use
* this as a starting point for your own threads.
*
* @author Jeff Heaton (http://www.jeffheaton.com)
* @version 1.0
*/
public class TestWorkerThread implements Runnable {
static private int count = 0;
private int taskNumber;
protected Done done;
/**
*
* @param done
*/
TestWorkerThread()
{
count++;
taskNumber = count;
}
public void run()
{
for (int i=0;i<100;i+=2) {
System.out.println("Task number: " + taskNumber +
",percent complete = " + i );
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
}
}
}
}
Listing 4—The ThreadPool Class
import com.heaton.threads.*;
/**
* Main class used to test the thread pool.
*
* @author Jeff Heaton (http://www.jeffheaton.com)
* @version 1.0
*/
public class TestThreadPool {
/**
* Main entry point.
*
* @param args No arguments are used.
*/
public static void main(String args[])
{
ThreadPool pool = new ThreadPool(10);
for (int i=1;i<25;i++) {
pool.assign(new TestWorkerThread());
}
pool.complete();
System.out.println("All tasks are done.");
}
}