ThreadWarningSystem.java
001 package gate.util;
002 
003 import java.lang.management.ManagementFactory;
004 import java.lang.management.ThreadInfo;
005 import java.lang.management.ThreadMXBean;
006 import java.util.ArrayList;
007 import java.util.Collection;
008 import java.util.HashSet;
009 import java.util.Set;
010 import java.util.Timer;
011 import java.util.TimerTask;
012 
013 /**
014  * http://www.javaspecialists.eu/archive/Issue093.html
015  */
016 public class ThreadWarningSystem {
017   private final Timer threadCheck = new Timer("Thread Monitor"true);
018   private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean();
019   private final Collection<Listener> listeners = new ArrayList<Listener>();
020   /**
021    * The number of milliseconds between checking for deadlocks.
022    * It may be expensive to check for deadlocks, and it is not
023    * critical to know so quickly.
024    */
025   private static final int DEADLOCK_CHECK_PERIOD = 500;
026   /**
027    * The number of milliseconds between checking number of
028    * threads.  Since threads can be created very quickly, we need
029    * to check this frequently.
030    */
031   private static final int THREAD_NUMBER_CHECK_PERIOD = 20;
032   private static final int MAX_STACK_DEPTH = 30;
033   private boolean threadThresholdNotified = false;
034   private Set<Long> deadlockedThreads = new HashSet<Long>();
035 
036   /**
037    * Monitor only deadlocks.
038    */
039   public ThreadWarningSystem() {
040     threadCheck.schedule(new TimerTask() {
041       @Override
042       public void run() {
043         long[] ids = mbean.findMonitorDeadlockedThreads();
044         if (ids != null && ids.length > 0) {
045           for (Long l : ids) {
046             if (!deadlockedThreads.contains(l)) {
047               deadlockedThreads.add(l);
048               ThreadInfo ti = mbean.getThreadInfo(l, MAX_STACK_DEPTH);
049               fireDeadlockDetected(ti);
050             }
051           }
052         }
053       }
054     }10, DEADLOCK_CHECK_PERIOD);
055   }
056 
057   /**
058    * Monitor deadlocks and the number of threads.
059    */
060   public ThreadWarningSystem(final int threadThreshold) {
061     this();
062     threadCheck.schedule(new TimerTask() {
063       @Override
064       public void run() {
065         if (mbean.getThreadCount() > threadThreshold) {
066           if (!threadThresholdNotified) {
067             fireThresholdExceeded();
068             threadThresholdNotified = true;
069           }
070         else {
071           threadThresholdNotified = false;
072         }
073       }
074     }10, THREAD_NUMBER_CHECK_PERIOD);
075   }
076 
077   private void fireDeadlockDetected(ThreadInfo thread) {
078     // In general I avoid using synchronized.  The surrounding
079     // code should usually be responsible for being threadsafe.
080     // However, in this case, the timer could be notifying at
081     // the same time as someone is adding a listener, and there
082     // is nothing the calling code can do to prevent that from
083     // occurring.  Another tip though is this: when I synchronize
084     // I use a private field to synchronize on, instead of
085     // "this".
086     synchronized (listeners) {
087       for (Listener l : listeners) {
088         l.deadlockDetected(thread);
089       }
090     }
091   }
092 
093   private void fireThresholdExceeded() {
094     ThreadInfo[] allThreads = mbean.getThreadInfo(mbean.getAllThreadIds());
095     synchronized (listeners) {
096       for (Listener l : listeners) {
097         l.thresholdExceeded(allThreads);
098       }
099     }
100   }
101 
102   public boolean addListener(Listener l) {
103     synchronized (listeners) {
104       return listeners.add(l);
105     }
106   }
107 
108   public boolean removeListener(Listener l) {
109     synchronized (listeners) {
110       return listeners.remove(l);
111     }
112   }
113 
114   /**
115    * This is called whenever a problem with threads is detected.
116    * The two events are deadlockDetected() and thresholdExceeded().
117    */
118   public interface Listener {
119     /**
120      @param deadlockedThread The deadlocked thread, with stack
121      * trace of limited depth.
122      */
123     void deadlockDetected(ThreadInfo deadlockedThread);
124     /**
125      @param allThreads All the threads in the JVM, without
126      * stack traces.
127      */
128     void thresholdExceeded(ThreadInfo[] allThreads);
129   }
130 }