package gate.util; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; /** * http://www.javaspecialists.eu/archive/Issue093.html */ public class ThreadWarningSystem { private final Timer threadCheck = new Timer("Thread Monitor", true); private final ThreadMXBean mbean = ManagementFactory.getThreadMXBean(); private final Collection<Listener> listeners = new ArrayList<Listener>(); /** * The number of milliseconds between checking for deadlocks. * It may be expensive to check for deadlocks, and it is not * critical to know so quickly. */ private static final int DEADLOCK_CHECK_PERIOD = 500; /** * The number of milliseconds between checking number of * threads. Since threads can be created very quickly, we need * to check this frequently. */ private static final int THREAD_NUMBER_CHECK_PERIOD = 20; private static final int MAX_STACK_DEPTH = 30; private boolean threadThresholdNotified = false; private Set<Long> deadlockedThreads = new HashSet<Long>(); /** * Monitor only deadlocks. */ public ThreadWarningSystem() { threadCheck.schedule(new TimerTask() { @Override public void run() { long[] ids = mbean.findMonitorDeadlockedThreads(); if (ids != null && ids.length > 0) { for (Long l : ids) { if (!deadlockedThreads.contains(l)) { deadlockedThreads.add(l); ThreadInfo ti = mbean.getThreadInfo(l, MAX_STACK_DEPTH); fireDeadlockDetected(ti); } } } } }, 10, DEADLOCK_CHECK_PERIOD); } /** * Monitor deadlocks and the number of threads. */ public ThreadWarningSystem(final int threadThreshold) { this(); threadCheck.schedule(new TimerTask() { @Override public void run() { if (mbean.getThreadCount() > threadThreshold) { if (!threadThresholdNotified) { fireThresholdExceeded(); threadThresholdNotified = true; } } else { threadThresholdNotified = false; } } }, 10, THREAD_NUMBER_CHECK_PERIOD); } private void fireDeadlockDetected(ThreadInfo thread) { // In general I avoid using synchronized. The surrounding // code should usually be responsible for being threadsafe. // However, in this case, the timer could be notifying at // the same time as someone is adding a listener, and there // is nothing the calling code can do to prevent that from // occurring. Another tip though is this: when I synchronize // I use a private field to synchronize on, instead of // "this". synchronized (listeners) { for (Listener l : listeners) { l.deadlockDetected(thread); } } } private void fireThresholdExceeded() { ThreadInfo[] allThreads = mbean.getThreadInfo(mbean.getAllThreadIds()); synchronized (listeners) { for (Listener l : listeners) { l.thresholdExceeded(allThreads); } } } public boolean addListener(Listener l) { synchronized (listeners) { return listeners.add(l); } } public boolean removeListener(Listener l) { synchronized (listeners) { return listeners.remove(l); } } /** * This is called whenever a problem with threads is detected. * The two events are deadlockDetected() and thresholdExceeded(). */ public interface Listener { /** * @param deadlockedThread The deadlocked thread, with stack * trace of limited depth. */ void deadlockDetected(ThreadInfo deadlockedThread); /** * @param allThreads All the threads in the JVM, without * stack traces. */ void thresholdExceeded(ThreadInfo[] allThreads); } }