Chapter 10. Thread Groups

IN THIS CHAPTER

In Java, threads can be grouped together and associated with an instance of ThreadGroup. In this chapter, I'll show you how to use some of the methods of ThreadGroup. At the end of the chapter, I'll show you how to use the class ThreadViewer to visually display the status of all the threads running in the Java VM.

In addition to the information in this chapter, a detailed description of the ThreadGroup API can be found in Appendix B at the end of this book.

What Are Thread Groups?

In Java, all threads belong to an instance of the ThreadGroup class. A thread group has a name and some properties associated with it and can be used to facilitate the management of threads within it as a group. Thread groups allow the threads of the VM to be organized and can provide some inter-group security. A ThreadGroup can contain other ThreadGroups. Figure 10.1 shows a sample containment tree for Thread and ThreadGroup objects. There is only one root thread group, and it contains all the other threads and groups. Each subgroup can contain other groups and threads. All threads belong to exactly one thread group. All thread groups (except for the root thread group) have exactly one parent thread group.

An example containment hierarchy for threads and thread groups.

Figure 10.1. An example containment hierarchy for threads and thread groups.

When a new ThreadGroup is constructed, it is added as a member of an existing ThreadGroup. If threadX is in the groupA thread group and it executes the code

ThreadGroup groupB = new ThreadGroup("groupB");

the newly formed thread group groupB has groupA as its parent group. If you want a parent thread group other than the default, you can specify the parent thread group at the time of construction. If threadX is in groupA and it executes the code

ThreadGroup groupD = new ThreadGroup(groupC, "groupD");

the newly formed groupD has groupC as its parent thread group, not groupA.

Like Thread, instances of ThreadGroup have names associated with them. Unlike Thread, a name must be specified—there is no option for names to be automatically generated. If threadX executes the code

ThreadGroup group = new ThreadGroup("comm subsystem");

a new ThreadGroup is created and named comm subsystem. To retrieve the name of a particular group, the getName() method is used. If you run the code

String groupName = group.getName();

groupName is the name passed to the constructor when group is instantiated: that is, comm subsystem.

New Thread objects default to be in the same thread group as the thread that does the construction. If threadX is in groupA and it executes the code

Thread threadY = new Thread(target);

threadY is in groupA, just like the thread (threadX) that constructed it. If a new thread should be in another group, the desired thread group can be passed to the constructor for Thread. If threadX is in groupA and it executes the code

Thread threadZ = new Thread(groupC, target);

threadZ is a member of groupC, not groupA.

Using getParent()

If you need to know which thread group a particular thread group belongs to, you can use the getParent() method on ThreadGroup. Suppose at some time in the past, this code was executed:

ThreadGroup groupD = new ThreadGroup(groupC, "groupD");

Then, later in the application, the following code is used:

ThreadGroup parentOfGroupD = groupD.getParent();

The reference returned and stored in parentOfGroupD refers to the same ThreadGroup instance as groupC does.

If getParent() is invoked on the root thread group, null is returned. In the VM, only one instance of ThreadGroup does not have a parent group, and that is the root group.

Finding the Subgroups of a Thread Group

The activeGroupCount() method returns the number of active thread groups in a particular ThreadGroup and all of its subgroups. Keep in mind that this can be dynamically changing, but it's an accurate snapshot at a moment in time.

To get a reference to all of the groups and subgroups of a ThreadGroup, the enumerate(ThreadGroup[]) method can be used. If you don't want to include a recursive search of the subgroups, use enumerate(ThreadGroup[], false) instead. Both methods return the number of groups copied into the array that is passed. If the array is not big enough, the extra groups are silently ignored. To get an idea of how big the destination array needs to be, you can use the value returned from activeGroupCount(). This code tries to capture all of the groups and subgroups of group:

ThreadGroup group = // ...
int estimatedSize = 2 * group.activeGroupCount();
ThreadGroup[] dest = new ThreadGroup[estimatedSize];
int actualSize = group.enumerate(dest);

The count returned from activeGroupCount() is doubled in an attempt to be sure that the destination array is large enough. Based on estimatedSize, a destination array for the groups is allocated and referred to by dest. The enumerate(ThreadGroup[]) method copies up to dest.length groups into dest. The number of groups copied is returned and stored in actualSize. If actualSize is equal to dest.length, there is a good chance that dest was not big enough. Generally, actualSize is less than dest.length and indicates the number of valid groups in dest.

Using the getThreadGroup() Method of Thread

If you need to know which thread group a thread belongs to, you can use the getThreadGroup() method on Thread. To find out which thread group threadX belongs to, use this code:

ThreadGroup groupForThreadX = threadX.getThreadGroup();

groupForThreadX is a reference to the ThreadGroup that threadX belongs to. If a thread is no longer alive, getThreadGroup() returns null instead of a ThreadGroup.

To determine which thread group the thread executing a block of code belongs to, use:

ThreadGroup group = Thread.currentThread().getThreadGroup();

Finding All the Threads in a Thread Group

The activeCount() method returns the number of active threads in a particular ThreadGroup and all of its subgroups. Keep in mind that this can be dynamically changing, as new threads might be created and existing threads might be dying.

To get a reference to all of the threads in a ThreadGroup and all of its subgroups, the enumerate(Thread[]) method can be used. If you don't want to include a recursive search of the subgroups, use enumerate(Thread[], false) instead. Both methods return the number of threads copied into the passed array. If the array is not big enough, the extra threads are silently ignored. To get an idea of how big the destination array needs to be, you can use the value returned from activeCount(). This code tries to capture all of the threads in group and its subgroups:

ThreadGroup group = // ...
int estimatedSize = 2 * group.activeCount();
Thread[] dest = new Thread[estimatedSize];
int actualSize = group.enumerate(dest);

The count returned from activeCount() is doubled in an attempt to be sure that the destination array is large enough. Based on estimatedSize, a destination array for the threads is allocated and referred to by dest. The enumerate(Thread[]) method copies up to dest.length threads into dest. The number of threads copied is returned and stored in actualSize. If actualSize is equal to dest.length, there is a good chance that dest was not big enough. Generally, actualSize is less than dest.length and indicates the number of valid threads in dest.

Understanding Thread Group Security

The checkAccess() method of ThreadGroup is called internally by many of the other ThreadGroup methods. It checks to see if a SecurityManager exists for a VM. By default, applications do not have a SecurityManager installed. Applets, on the other hand, might have one.

If a SecurityManager exists and it determines that a particular thread is not permitted to take an action, it throws a SecurityException. SecurityException is a subclass of RuntimeException, so try/catch blocks are typically not used. If no SecurityManager isinstalled, or if the SecurityManager approves of the access, checkAccess() silently returns.

A full discussion of security in Java is beyond the scope of this book. You just need to be aware that a SecurityException might be thrown from most of the methods of ThreadGroup. If you are writing an application, you can usually safely ignore these checkAccess() issues.

Using setMaxPriority() and getMaxPriority()

The setMaxPriority() method sets the maximum priority that all threads in a particular thread group and all of its subgroups can have. Threads that are already running at a higher priority are not affected. This method has an effect when new threads are constructed or when the setPriority() method of Thread is invoked.

If setPriority() is called with a priority higher than the maximum allowed for the group that a thread is in, the priority is silently lowered to the maximum allowed. The getMaxPriority() method returns the current maximum priority permitted for threads in a particular thread group.

Using interrupt()

The interrupt() method of ThreadGroup can be used to signal an interrupt to all the threads in the group and subgroups. This method can be useful if several threads have been spawned to handle a task, and it's time to signal all of them to shut down. (See Chapter 5, "Gracefully Stopping Threads," for more on signaling threads to die by interrupting them.)

Deprecated Methods: stop(), suspend(), and resume()

The stop(), suspend(), and resume() methods of ThreadGroup have all been deprecated as of JDK 1.2 and should not be used. The stop() method stops all the threads in the specified thread group and subgroups. The suspend() method suspends execution of all of the threads in the specified thread group and subgroups. The resume() method resumes execution of all of the threads in the specified thread group and subgroups. These methods are deprecated at the ThreadGroup level for the same good reasons that they are deprecated at the Thread level. See Chapter 5 for more information about why these methods are deprecated and ways that you can safely implement similar behavior.

Class ThreadViewer

The class ThreadViewer (see Listing 10.1) graphically displays all of the threads currently running in the Java VM. It automatically refreshes itself every 5 seconds to keep current. ThreadViewer can be a handy tool to have around during the development and debugging of multithreaded applications.

Example 10.1. ThreadViewer.java—Displays All of the Currently Running Threads in a Table

 1: import java.awt.*;
 2: import java.awt.event.*;
 3: import javax.swing.*;
 4: import javax.swing.table.*;
 5:
 6: public class ThreadViewer extends JPanel {
 7:     private ThreadViewerTableModel tableModel;
 8:
 9:     public ThreadViewer() {
10:         tableModel = new ThreadViewerTableModel();
11:
12:         JTable table = new JTable(tableModel);
13:         table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
14:
15:         TableColumnModel colModel = table.getColumnModel();
16:         int numColumns = colModel.getColumnCount();
17:
18:         // manually size all but the last column
19:         for ( int i = 0; i < numColumns - 1; i++ ) {
20:             TableColumn col = colModel.getColumn(i);
21:
22:             col.sizeWidthToFit();
23:             col.setPreferredWidth(col.getWidth() + 5);
24:             col.setMaxWidth(col.getWidth() + 5);
25:         }
26:
27:         JScrollPane sp = new JScrollPane(table);
28:
29:         setLayout(new BorderLayout());
30:         add(sp, BorderLayout.CENTER);
31:     }
32:
33:     public void dispose() {
34:         tableModel.stopRequest();
35:     }
36:
37:     protected void finalize() throws Throwable {
38:         dispose();
39:     }
40: 
41:     public static JFrame createFramedInstance() {
42:         final ThreadViewer viewer = new ThreadViewer();
43:
44:         final JFrame f = new JFrame("ThreadViewer");
45:         f.addWindowListener(new WindowAdapter() {
46:                 public void windowClosing(WindowEvent e) {
47:                     f.setVisible(false);
48:                     f.dispose();
49:                     viewer.dispose();
50:                 }
51:             });
52:
53:         f.setContentPane(viewer);
54:         f.setSize(500, 300);
55:         f.setVisible(true);
56:
57:         return f;
58:     }
59:    
60:     public static void main(String[] args) {
61:         JFrame f = ThreadViewer.createFramedInstance();
62:
63:         // For this example, exit the VM when the viewer
64:         // frame is closed.
65:         f.addWindowListener(new WindowAdapter() {
66:                 public void windowClosing(WindowEvent e) {
67:                     System.exit(0);
68:                 }
69:             });
70:
71:         // Keep the main thread from exiting by blocking
72:         // on wait() for a notification that never comes.
73:         Object lock = new Object();
74:         synchronized ( lock ) {
75:             try {
76:                 lock.wait();
77:             } catch ( InterruptedException x ) {
78:             }
79:         }
80:     }
81: }

ThreadViewer extends JPanel (line 6) so that it can be placed in any container. Typically, you would invoke the static method ThreadViewer.createFramedInstance() (lines 41–58) to have a new instance of ThreadViewer be automatically put into a JFrame and displayed.

In the constructor (lines 9–31), a new ThreadViewerTableModel (see Listing 10.2) is created and passed to the constructor for JTable (lines 10–12). The rest of the constructor takes care of setting the column sizing options and puts the JTable into a JScrollPane in case it gets quite large.

The dispose() method (lines 33–35) should be called when you are done with an instance of ThreadViewer. It sends a message to the thread running inside the model to stop soon. I'll explain more about the model and the thread running within it later.

The static method createFramedInstance() (lines 41–58) creates an instance of ThreadViewer and puts it into a JFrame. The JFrame is sized, displayed, and returned to the caller (lines 54–57). When the user closes the frame, the frame is hidden and disposed, and the dispose() method of ThreadViewer is also called (lines 46–50).

In main() (lines 60–80), a framed instance of ThreadViewer is created. To keep the main thread from exiting the main() method, the thread waits on an arbitrary object for notification that will never come (lines 73–79). This is only done for demonstration purposes so that the main thread is listed in the ThreadViewer window.

The JTable in ThreadViewer gets its data from a ThreadViewerTableModel (see Listing 10.2).

Example 10.2. ThreadViewerTableModel.java—Represents the Current List of Threads as a TableModel for JTable

  1: import java.awt.*;
  2: import java.lang.reflect.*;
  3: import javax.swing.*;
  4: import javax.swing.table.*;
  5:
  6: public class ThreadViewerTableModel extends AbstractTableModel {
  7:     private Object dataLock;
  8:     private int rowCount;
  9:     private Object[][] cellData;
 10:     private Object[][] pendingCellData;
 11:
 12:     // the column information remains constant
 13:     private final int columnCount;
 14:     private final String[] columnName;
 15:     private final Class[] columnClass;
 16:
 17:     // self-running object control variables
 18:     private Thread internalThread;
 19:     private volatile boolean noStopRequested;
 20:
 21:     public ThreadViewerTableModel() {
 22:         rowCount = 0;
 23:         cellData = new Object[0][0];
 24:
 25:         // JTable uses this information for the column headers
 26:         String[] names = { 
 27:             "Priority", "Alive",
 28:             "Daemon", "Interrupted",
 29:             "ThreadGroup", "Thread Name" } ;
 30:         columnName = names;                        
 31:                        
 32:         // JTable uses this information for cell rendering
 33:         Class[] classes = { 
 34:             Integer.class, Boolean.class,
 35:             Boolean.class, Boolean.class,
 36:             String.class, String.class } ;
 37:         columnClass = classes;
 38:
 39:         columnCount = columnName.length;
 40: 
 41:         // used to control concurrent access
 42:         dataLock = new Object();
 43:
 44:         noStopRequested = true;
 45:         Runnable r = new Runnable() {
 46:                 public void run() {
 47:                     try {
 48:                         runWork();
 49:                     } catch ( Exception x ) {
 50:                         // in case ANY exception slips through
 51:                         x.printStackTrace();
 52:                     }
 53:                 }
 54:             };
 55:
 56:         internalThread = new Thread(r, "ThreadViewer");
 57:         internalThread.setPriority(Thread.MAX_PRIORITY - 2);
 58:         internalThread.setDaemon(true);
 59:         internalThread.start();
 60:     }
 61:
 62:     private void runWork() {
 63:
 64:         // The run() method of transferPending is called by
 65:         // the event handling thread for safe concurrency.
 66:         Runnable transferPending = new Runnable() {
 67:                 public void run() {
 68:                     transferPendingCellData();
 69:
 70:                     // Method of AbstractTableModel that
 71:                     // causes the table to be updated.
 72:                     fireTableDataChanged();
 73:                 }
 74:             };
 75:
 76:         while ( noStopRequested ) {
 77:             try {
 78:                 createPendingCellData();
 79:                 SwingUtilities.invokeAndWait(transferPending);
 80:                 Thread.sleep(5000); 
 81:             } catch ( InvocationTargetException tx ) {
 82:                 tx.printStackTrace();
 83:                 stopRequest();
 84:             } catch ( InterruptedException x ) {
 85:                 Thread.currentThread().interrupt();
 86:             }
 87:         }
 88:     }
 89:
 90:     public void stopRequest() {
 91:         noStopRequested = false;
 92:         internalThread.interrupt();
 93:     }
 94:
 95:     public boolean isAlive() {
 96:         return internalThread.isAlive();
 97:     }
 98:
 99:     private void createPendingCellData() {
100:         // this method is called by the internal thread
101:         Thread[] thread = findAllThreads();
102:         Object[][] cell = new Object[thread.length][columnCount];
103:
104:         for ( int i = 0; i < thread.length; i++ ) {
105:             Thread t = thread[i];
106:             Object[] rowCell = cell[i];
107:
108:             rowCell[0] = new Integer(t.getPriority());
109:             rowCell[1] = new Boolean(t.isAlive());
110:             rowCell[2] = new Boolean(t.isDaemon());
111:             rowCell[3] = new Boolean(t.isInterrupted());
112:             rowCell[4] = t.getThreadGroup().getName();
113:             rowCell[5] = t.getName();
114:         }
115:
116:         synchronized ( dataLock ) {
117:             pendingCellData = cell;
118:         }
119:     }
120: 
121:     private void transferPendingCellData() {
122:         // this method is called by the event thread
123:         synchronized ( dataLock ) {
124:             cellData = pendingCellData;
125:             rowCount = cellData.length;
126:         }
127:     }
128:
129:     public int getRowCount() {
130:         // this method is called by the event thread
131:         return rowCount;
132:     }
133:    
134:     public Object getValueAt(int row, int col) {
135:         // this method is called by the event thread
136:         return cellData[row][col];
137:     }
138:
139:     public int getColumnCount() {
140:         return columnCount;
141:     }
142:
143:     public Class getColumnClass(int columnIdx) {
144:         return columnClass[columnIdx];
145:     }
146:
147:     public String getColumnName(int columnIdx) {
148:         return columnName[columnIdx];
149:     }
150:
151:     public static Thread[] findAllThreads() {
152:         ThreadGroup group =
153:             Thread.currentThread().getThreadGroup();
154:
155:         ThreadGroup topGroup = group;
156:
157:         // traverse the ThreadGroup tree to the top
158:         while ( group != null ) {
159:             topGroup = group;
160:             group = group.getParent();
161:         }
162: 
163:         // Create a destination array that is about
164:         // twice as big as needed to be very confident
165:         // that none are clipped.
166:         int estimatedSize = topGroup.activeCount() * 2;
167:         Thread[] slackList = new Thread[estimatedSize];
168:
169:         // Load the thread references into the oversized
170:         // array. The actual number of threads loaded
171:         // is returned.
172:         int actualSize = topGroup.enumerate(slackList);
173:
174:         // copy into a list that is the exact size
175:         Thread[] list = new Thread[actualSize];
176:         System.arraycopy(slackList, 0, list, 0, actualSize);
177:
178:         return list;
179:     }
180: }

ThreadViewerTableModel extends AbstractTableModel (line 6) and holds the data to be displayed in the JTable. The event thread comes in and invokes various methods to determine what should be drawn on the screen. The model has an internal thread that refreshes the list of threads every 5 seconds and updates the model data to reflect the new state. To control concurrent access by the internal thread and the event thread, the dataLock object (lines 7, 42) is used for synchronization. rowCount reflects the most recent polling of the number of threads (line 8). The two-dimensional array cellData (line 9) is read by the event thread to determine what to draw for each cell of the table. The other two-dimensional array pendingCellData (line 10) is populated by the internal thread and copied into cellData by the event thread when it is complete.

In the constructor (lines 21–60), the number of rows is initialized to 0 and a zero-sized cellData is created. This step is necessary in case the event thread starts calling methods on the model before a list of threads has been formatted. The names of the columns are constant and are stored in columnName (lines 26–30). The datatypes for each column are stored in columnClass (lines 32–37). These types are queried by the event thread and are used to determine how to render the cell data in the table. An internal thread is started to refresh the data every 5 seconds (lines 44–59). The priority of the internal thread is set at 8 so that it is a relatively high-priority thread (line 57). The internal thread spends most of its time sleeping anyway. This internal thread is set to be a daemon thread because there is no point of it running if every other nondaemon thread is done (line 58).

The internal thread invokes runWork() (lines 62–88). A Runnable is constructed and referred by transferPending (lines 66–74). transferPending is used to bundle code for the event thread and is passed to SwingUtilities.invokeAndWait() (line 79). Inside transferPending, the event thread calls transferPendingCellData() followed by fireTableDataChanged() to get the table to display the new values. The internal thread loops (lines 76–87) in the while until another thread invokes the stopRequest() method. Inside the while loop, createPendingCellData() is called to check the running threads again and build up the new cell data (line 78). Then SwingUtilities.invokeAndWait() is called to get the event thread to read the new data (line 79). Before looping again, the internal thread sleeps for 5 seconds (line 80).

When the internal thread invokes createPendingCellData() (lines 99–119), the current list of threads is built by calling findAllThreads() (line 101). The new data is first put into the local variable cell (line 102). The dimensions of cell are based on the number of columns (fixed) and the number of threads that were just found. For each thread found, the thread's priority, alive status, daemon status, interrupted status, thread group name, and thread name are gathered and stored into cell (lines 104–114). After locking on dataLock, pendingData is set to refer to the two-dimensional array referred to by cell (lines 116–118).

Next, the event thread invokes transferPendingCellData() (lines 121–127). After locking on dataLock, cellData is set to refer to pendingCellData and rowCount is updated to match the number of cells. The most recent data is now accessible through the cellData reference.

The public and static method findAllThreads() (lines 151–179) can be used from anywhere to return a Thread[] with the most recent list of running threads. In ThreadViewer, the internal thread invokes findAllThreads() every 5 seconds to update the table model. First, the ThreadGroup of the invoking thread is determined (lines 152–153). From this starting point, the ThreadGroup containment tree is traversed to the root thread group (lines 155–161). After topGroup is determined, the number of threads is retrieved using activeCount() (line 166). This count is doubled to get an estimate that should be large enough (line 166). A new array of Thread objects (slackList) is allocated based on the estimated count (line 167). The enumerate() method is invoked on topGroup and fills slackList with all the threads it finds (line 172). The actual number of threads found is returned by enumerate(). A new Thread[] of the exact size is allocated (line 175), and the Thread references are copied into it from slackList (line 176). Finally, the exact-sized array is returned to the caller (line 178).

Figure 10.2 shows a sample screenshot produced when ThreadViewer is run. You can see that quite a few threads are running in the VM. The row for the internal thread is highlighted. Its name is ThreadViewer, and you can see that its priority is 8 and that it is a daemon thread. Because main is blocked and still running, you can see its entry too. Currently, none of the threads is interrupted. Generally, a thread only has interrupted status briefly before it is reset and an InterruptedException is thrown.

ThreadViewer in action.

Figure 10.2. ThreadViewer in action.

Summary

ThreadGroup can help to organize the threads of a VM into groups. In this chapter, I showed you some of the methods that are available to work on thread groups. In particular, the getParent(), getName(), activeCount(), and enumerate() methods of ThreadGroup were used along with the getThreadGroup() method of Thread to produce the data for ThreadViewer. ThreadViewer can be a very useful tool for code development and debugging.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.195.215