Overview

Chapter 10 - Thread Groups

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

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.
Listing 10.1  ThreadViewer.javaDisplays 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 4158) to have a new instance of ThreadViewer be automatically put into a JFrame and displayed.
In the constructor (lines 931), a new ThreadViewerTableModel (see Listing 10.2 ) is created and passed to the constructor for JTable (lines 1012). 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 3335) 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. Ill explain more about the model and the thread running within it later.
The static method createFramedInstance() (lines 4158) creates an instance of ThreadViewer and puts it into a JFrame . The JFrame is sized , displayed, and returned to the caller (lines 5457). When the user closes the frame, the frame is hidden and disposed, and the dispose() method of ThreadViewer is also called (lines 4650).
In main() (lines 6080), 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 7379). 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).
Listing 10.2  ThreadViewerTableModel.javaRepresents 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 2160), the number of rows is initialized to 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 2630). The datatypes for each column are stored in columnClass (lines 3237). 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 4459). 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 6288). A Runnable is constructed and referred by transferPending (lines 6674). 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 7687) 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 99119), 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 threads priority, alive status, daemon status, interrupted status, thread group name, and thread name are gathered and stored into cell (lines 104114). After locking on dataLock , pendingData is set to refer to the two-dimensional array referred to by cell (lines 116118).
Next, the event thread invokes transferPendingCellData() (lines 121127). 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 151179) 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 152153). From this starting point, the ThreadGroup containment tree is traversed to the root thread group (lines 155161). 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.
Figure 10.2: ThreadViewer in action.

Toc


Java Thread Programming
Java Thread Programming
ISBN: 0672315858
EAN: 2147483647
Year: 2005
Pages: 149
Authors: Paul Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net