Microsoft & .NET.NETImproving Thread Management with Thread Groups

Improving Thread Management with Thread Groups

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Multi-threaded programming is the source of many headaches for developers, particularly when large numbers of threads are running. Managing these threads is a difficult task. As the number of threads increases, system performance is affected, and it is often advantageous to adjust thread priorities of specific threads, suspend certain threads, or stop them entirely. While it is certainly possible to do this on an individual basis, a simpler solution is to use thread groups, which makes it possible to apply changes to a large number of threads at once. Thread groups also offer other advantages, such as keeping an automatic tally of how many active threads belong to a group, as well as an opportunity to improve thread debugging, by catching uncaught exceptions that would normally be missed.

Grouping Related Threads Together

The need for grouping threads together is very strong. In most applications, threads can be divided into several groups. Some, for example, perform background programming, while others update the user interface and respond to user interaction, or perform tasks in the foreground. To maintain a list of threads, an array or a data structure from the Java Collections framework might be used — but a better alternative is the

java.lang.ThreadGroup
class
.

The

ThreadGroup
class is a special type of container, designed specifically with threads in mind. Thread groups may contain individual threads, as well as subgroups of threads. The

ThreadGroup
class provides methods to access individual elements of the group via the

enumerate(..)
method, which copies either threads or thread groups into an array. It also provides methods to determine the number of active threads (i.e., threads that have not exited their

run()
method). Additionally the thread group class provides a way to access a count of the number of active groups (i.e., the number of groups and subgroups with active threads).

Figure 1. Thread groups, of course, allow many threads to be grouped and nested.

Creating a thread group is extremely simple. There are two constructors, one of which takes as a parameter a String (which names a group) and the other, which also takes a parent group. This allows groups to be nested (a subgroup with a group as a parent).


public ThreadGroup ( String name )
public ThreadGroup ( ThreadGroup parent, String name)

Threads are grouped together when first constructed. The

java.lang.Thread
class provides several constructors, which take a

ThreadGroup
as a parameter. Of course, threads can be created without assigning them to a specific group, but they will be grouped nonetheless. A group is created by the JVM at startup, which runs the primary application thread. So unless you select a specific group, one will be set for you. The following example shows the construction of a thread group and how to specify the group of a thread:


ThreadGroup somegroup = new ThreadGroup ("GroupA");
Thread t = new Thread ( somegroup, "dummy-thread");

Accessing Individual Threads

If access to individual threads is necessary, a list of active threads can be obtained, by calling the

ThreadGroup.enumerate(..)
method. There are several overloaded versions of this method, which also allow you to select groups that are subgroups of a given

ThreadGroup
object, as well as to recursively search in all subgroups for a list of threads. All of the

enumerate
methods require you to create an instance of a Thread array, or a

ThreadGroup
array, with enough size to hold each item. To determine the number of active threads/groups, you can call either the

ThreadGroup.activeCount()
or

ThreadGroup.activeGroupCount()
, respectively.

Let’s look at an example of how this would be done. Suppose we have an existing group

theGroup
, with an uncertain number of threads (since some active threads may have terminated, and the number is not known).


// Get the number of active threads
int activeThreadCount = theGroup.activeCount();

// Create an array of threads, to hold all of the active threads
Thread[] threadArray = new Thread[activeThreadCount];

// Copy to array
theGroup.enumerate(threadArray);

Once a list is obtained, operations may be performed selectively on each thread, or if a custom subclass of a thread was used, it may be cast as such. For example, if I created a

WorkerThread
class which extended

java.lang.Thread
, it could be cast back to a

WorkerThread
, and then operations could be performed (such as accessing member variables or methods).


for (int i = 0; i < threadArray.length; i++)
{
// *Always* check using an instanceof operator, to prevent
// a java.lang.ClassCastException being thrown
if (threadArray[i] instanceof WorkerThread)
{
// Perform cast
WorkerThread worker = (WorkerThread) threadArray[i] ;

Object obj = worker.getData();

// do something with data …..
}
}

Performing Thread Operations En Masse

While working with a thread individually is necessary sometimes, a simpler alternative is to perform operations en masse, using thread groups. The ThreadGroup class offers several methods that represent common thread operations, such as suspending, resuming, interrupting, and even stopping threads. Alas, there is no start method for a thread group — which would be a useful shortcut. Nonetheless, the other methods make it quite simple to perform thread operations collectively, rather than individually.

You might ask, why would this be handy? Consider the amount of code required to access an array of active threads and then looping through each and every element of the array to perform the operation. Now, consider how much code is required to suspend all active threads in a group:


// ThreadGroup theGroup
theGroup.suspend(); // interrupt all active threads

The following methods of the ThreadGroup class apply to all active threads:

  • ThreadGroup.stop();
  • ThreadGroup.suspend();
  • ThreadGroup.interrupt();
  • ThreadGroup.resume();

Note: Readers should be aware that the stop, suspend, and resume methods have been deprecated as of JDK 1.2, due to a potential problem which may arise when the

stop()
and

suspend()
methods of

java.lang.Thread
are invoked. Any object locks held by these threads are immediately released (to prevent a deadlock arising when another thread tries to take out a lock owned by a dead or dormant thread). However, the object protected by the lock might be left in an inconsistent state. For this reason, these methods should be used sparingly and only where absolutely necessary.

Modifying Thread Priorities

While it isn’t possible to specify the priority of a group and all the threads within it (though this functionality would be very handy), it is possible to impose a cap on the upper limit of a thread group’s priority, by calling the

ThreadGroup.setMaxPriority(int)
method. For example, suppose we wanted a limit of 7, we’d use code similar to the following:


theGroup.setMaxPriority (7);

Improving Error Handling with Thread Groups

What’s the most annoying part of running Java software for users? My vote would be uncaught exceptions, which occur only at runtime and not compile time, and almost never during testing and debugging. The exception-handling features of Java are very good, but methods are not forced to declare methods that extend the

java.lang.RuntimeException
. This means you won’t catch them during compilation, and as they happen irregularly, you may not notice them during testing.

Sure you can add a try/catch block that catches a

RuntimeException
to the

run()
method of a

Thread
or

Runnable
subclass, but what if you want to handle error conditions in a central location, within one thread? The answer is, again, using thread groups!

The

ThreadGroup
class defines a method,

uncaughtException(Thread
thread, Throwable exception)
, which is invoked whenever a thread fails to catch an exception at runtime. By default, this method doesn’t do anything useful, but by overriding this method you can create a custom handler. Let’s look at a sample class, which logs the exceptions of any threads and then terminates.

Listing 1. Creating a custom handler.

By creating an instance of this custom thread group and assigning all threads to it as they are created, you can be assured that any thread that causes an error will be logged, and a user-friendly error message displayed (rather than the default action of a decidedly nasty stack trace).

Summary

Thread groups can make life much simpler for programmers, as well as improving error handling. While threads can be stored collectively in an array or Vector, the additional functionality provided by thread groups make them a more attractive option. By grouping threads collectively, you can also have clearer code and an easy way to track how many threads are active.

About the author

David Reilly is a software engineer and freelance technical writer living in Australia. A Sun Certified Java 1.1 Programmer, his research interests include the Java programming language, networking & distributed systems, and software agents. He can be reached via email at java@davidreilly.com or through his personal site.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories