Java tutorial
/* * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang; import java.io.PrintStream; import java.util.Arrays; /** * A thread group represents a set of threads. In addition, a thread * group can also include other thread groups. The thread groups form * a tree in which every thread group except the initial thread group * has a parent. * <p> * A thread is allowed to access information about its own thread * group, but not to access information about its thread group's * parent thread group or any other thread groups. * * @author unascribed * @since 1.0 */ /* The locking strategy for this code is to try to lock only one level of the * tree wherever possible, but otherwise to lock from the bottom up. * That is, from child thread groups to parents. * This has the advantage of limiting the number of locks that need to be held * and in particular avoids having to grab the lock for the root thread group, * (or a global lock) which would be a source of contention on a * multi-processor system with many thread groups. * This policy often leads to taking a snapshot of the state of a thread group * and working off of that snapshot, rather than holding the thread group locked * while we work on the children. */ public class ThreadGroup implements Thread.UncaughtExceptionHandler { private final ThreadGroup parent; String name; int maxPriority; boolean destroyed; boolean daemon; int nUnstartedThreads = 0; int nthreads; Thread threads[]; int ngroups; ThreadGroup groups[]; /** * Creates an empty Thread group that is not in any Thread group. * This method is used to create the system Thread group. */ private ThreadGroup() { // called from C code this.name = "system"; this.maxPriority = Thread.MAX_PRIORITY; this.parent = null; } /** * Constructs a new thread group. The parent of this new group is * the thread group of the currently running thread. * <p> * The {@code checkAccess} method of the parent thread group is * called with no arguments; this may result in a security exception. * * @param name the name of the new thread group. * @throws SecurityException if the current thread cannot create a * thread in the specified thread group. * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 */ public ThreadGroup(String name) { this(Thread.currentThread().getThreadGroup(), name); } /** * Creates a new thread group. The parent of this new group is the * specified thread group. * <p> * The {@code checkAccess} method of the parent thread group is * called with no arguments; this may result in a security exception. * * @param parent the parent thread group. * @param name the name of the new thread group. * @throws NullPointerException if the thread group argument is * {@code null}. * @throws SecurityException if the current thread cannot create a * thread in the specified thread group. * @see java.lang.SecurityException * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 */ public ThreadGroup(ThreadGroup parent, String name) { this(checkParentAccess(parent), parent, name); } private ThreadGroup(Void unused, ThreadGroup parent, String name) { this.name = name; this.maxPriority = parent.maxPriority; this.daemon = parent.daemon; this.parent = parent; parent.add(this); } /* * @throws NullPointerException if the parent argument is {@code null} * @throws SecurityException if the current thread cannot create a * thread in the specified thread group. */ private static Void checkParentAccess(ThreadGroup parent) { parent.checkAccess(); return null; } /** * Returns the name of this thread group. * * @return the name of this thread group. * @since 1.0 */ public final String getName() { return name; } /** * Returns the parent of this thread group. * <p> * First, if the parent is not {@code null}, the * {@code checkAccess} method of the parent thread group is * called with no arguments; this may result in a security exception. * * @return the parent of this thread group. The top-level thread group * is the only thread group whose parent is {@code null}. * @throws SecurityException if the current thread cannot modify * this thread group. * @see java.lang.ThreadGroup#checkAccess() * @see java.lang.SecurityException * @see java.lang.RuntimePermission * @since 1.0 */ public final ThreadGroup getParent() { if (parent != null) parent.checkAccess(); return parent; } /** * Returns the maximum priority of this thread group. Threads that are * part of this group cannot have a higher priority than the maximum * priority. * * @return the maximum priority that a thread in this thread group * can have. * @see #setMaxPriority * @since 1.0 */ public final int getMaxPriority() { return maxPriority; } /** * Tests if this thread group is a daemon thread group. A * daemon thread group is automatically destroyed when its last * thread is stopped or its last thread group is destroyed. * * @return {@code true} if this thread group is a daemon thread group; * {@code false} otherwise. * @since 1.0 */ public final boolean isDaemon() { return daemon; } /** * Tests if this thread group has been destroyed. * * @return true if this object is destroyed * @since 1.1 */ public synchronized boolean isDestroyed() { return destroyed; } /** * Changes the daemon status of this thread group. * <p> * First, the {@code checkAccess} method of this thread group is * called with no arguments; this may result in a security exception. * <p> * A daemon thread group is automatically destroyed when its last * thread is stopped or its last thread group is destroyed. * * @param daemon if {@code true}, marks this thread group as * a daemon thread group; otherwise, marks this * thread group as normal. * @throws SecurityException if the current thread cannot modify * this thread group. * @see java.lang.SecurityException * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 */ public final void setDaemon(boolean daemon) { checkAccess(); this.daemon = daemon; } /** * Sets the maximum priority of the group. Threads in the thread * group that already have a higher priority are not affected. * <p> * First, the {@code checkAccess} method of this thread group is * called with no arguments; this may result in a security exception. * <p> * If the {@code pri} argument is less than * {@link Thread#MIN_PRIORITY} or greater than * {@link Thread#MAX_PRIORITY}, the maximum priority of the group * remains unchanged. * <p> * Otherwise, the priority of this ThreadGroup object is set to the * smaller of the specified {@code pri} and the maximum permitted * priority of the parent of this thread group. (If this thread group * is the system thread group, which has no parent, then its maximum * priority is simply set to {@code pri}.) Then this method is * called recursively, with {@code pri} as its argument, for * every thread group that belongs to this thread group. * * @param pri the new priority of the thread group. * @throws SecurityException if the current thread cannot modify * this thread group. * @see #getMaxPriority * @see java.lang.SecurityException * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 */ public final void setMaxPriority(int pri) { int ngroupsSnapshot; ThreadGroup[] groupsSnapshot; synchronized (this) { checkAccess(); if (pri < Thread.MIN_PRIORITY || pri > Thread.MAX_PRIORITY) { return; } maxPriority = (parent != null) ? Math.min(pri, parent.maxPriority) : pri; ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } for (int i = 0; i < ngroupsSnapshot; i++) { groupsSnapshot[i].setMaxPriority(pri); } } /** * Tests if this thread group is either the thread group * argument or one of its ancestor thread groups. * * @param g a thread group. * @return {@code true} if this thread group is the thread group * argument or one of its ancestor thread groups; * {@code false} otherwise. * @since 1.0 */ public final boolean parentOf(ThreadGroup g) { for (; g != null; g = g.parent) { if (g == this) { return true; } } return false; } /** * Determines if the currently running thread has permission to * modify this thread group. * <p> * If there is a security manager, its {@code checkAccess} method * is called with this thread group as its argument. This may result * in throwing a {@code SecurityException}. * * @throws SecurityException if the current thread is not allowed to * access this thread group. * @see java.lang.SecurityManager#checkAccess(java.lang.ThreadGroup) * @since 1.0 */ public final void checkAccess() { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkAccess(this); } } /** * Returns an estimate of the number of active threads in this thread * group and its subgroups. Recursively iterates over all subgroups in * this thread group. * * <p> The value returned is only an estimate because the number of * threads may change dynamically while this method traverses internal * data structures, and might be affected by the presence of certain * system threads. This method is intended primarily for debugging * and monitoring purposes. * * @return an estimate of the number of active threads in this thread * group and in any other thread group that has this thread * group as an ancestor * * @since 1.0 */ public int activeCount() { int result; // Snapshot sub-group data so we don't hold this lock // while our children are computing. int ngroupsSnapshot; ThreadGroup[] groupsSnapshot; synchronized (this) { if (destroyed) { return 0; } result = nthreads; ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } for (int i = 0; i < ngroupsSnapshot; i++) { result += groupsSnapshot[i].activeCount(); } return result; } /** * Copies into the specified array every active thread in this * thread group and its subgroups. * * <p> An invocation of this method behaves in exactly the same * way as the invocation * * <blockquote> * {@linkplain #enumerate(Thread[], boolean) enumerate}{@code (list, true)} * </blockquote> * * @param list * an array into which to put the list of threads * * @return the number of threads put into the array * * @throws SecurityException * if {@linkplain #checkAccess checkAccess} determines that * the current thread cannot access this thread group * * @since 1.0 */ public int enumerate(Thread list[]) { checkAccess(); return enumerate(list, 0, true); } /** * Copies into the specified array every active thread in this * thread group. If {@code recurse} is {@code true}, * this method recursively enumerates all subgroups of this * thread group and references to every active thread in these * subgroups are also included. If the array is too short to * hold all the threads, the extra threads are silently ignored. * * <p> An application might use the {@linkplain #activeCount activeCount} * method to get an estimate of how big the array should be, however * <i>if the array is too short to hold all the threads, the extra threads * are silently ignored.</i> If it is critical to obtain every active * thread in this thread group, the caller should verify that the returned * int value is strictly less than the length of {@code list}. * * <p> Due to the inherent race condition in this method, it is recommended * that the method only be used for debugging and monitoring purposes. * * @param list * an array into which to put the list of threads * * @param recurse * if {@code true}, recursively enumerate all subgroups of this * thread group * * @return the number of threads put into the array * * @throws SecurityException * if {@linkplain #checkAccess checkAccess} determines that * the current thread cannot access this thread group * * @since 1.0 */ public int enumerate(Thread list[], boolean recurse) { checkAccess(); return enumerate(list, 0, recurse); } private int enumerate(Thread list[], int n, boolean recurse) { int ngroupsSnapshot = 0; ThreadGroup[] groupsSnapshot = null; synchronized (this) { if (destroyed) { return n; } int nt = nthreads; if (nt > list.length - n) { nt = list.length - n; } for (int i = 0; i < nt; i++) { if (threads[i].isAlive()) { list[n++] = threads[i]; } } if (recurse) { ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } } if (recurse) { for (int i = 0; i < ngroupsSnapshot; i++) { n = groupsSnapshot[i].enumerate(list, n, true); } } return n; } /** * Returns an estimate of the number of active groups in this * thread group and its subgroups. Recursively iterates over * all subgroups in this thread group. * * <p> The value returned is only an estimate because the number of * thread groups may change dynamically while this method traverses * internal data structures. This method is intended primarily for * debugging and monitoring purposes. * * @return the number of active thread groups with this thread group as * an ancestor * * @since 1.0 */ public int activeGroupCount() { int ngroupsSnapshot; ThreadGroup[] groupsSnapshot; synchronized (this) { if (destroyed) { return 0; } ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } int n = ngroupsSnapshot; for (int i = 0; i < ngroupsSnapshot; i++) { n += groupsSnapshot[i].activeGroupCount(); } return n; } /** * Copies into the specified array references to every active * subgroup in this thread group and its subgroups. * * <p> An invocation of this method behaves in exactly the same * way as the invocation * * <blockquote> * {@linkplain #enumerate(ThreadGroup[], boolean) enumerate}{@code (list, true)} * </blockquote> * * @param list * an array into which to put the list of thread groups * * @return the number of thread groups put into the array * * @throws SecurityException * if {@linkplain #checkAccess checkAccess} determines that * the current thread cannot access this thread group * * @since 1.0 */ public int enumerate(ThreadGroup list[]) { checkAccess(); return enumerate(list, 0, true); } /** * Copies into the specified array references to every active * subgroup in this thread group. If {@code recurse} is * {@code true}, this method recursively enumerates all subgroups of this * thread group and references to every active thread group in these * subgroups are also included. * * <p> An application might use the * {@linkplain #activeGroupCount activeGroupCount} method to * get an estimate of how big the array should be, however <i>if the * array is too short to hold all the thread groups, the extra thread * groups are silently ignored.</i> If it is critical to obtain every * active subgroup in this thread group, the caller should verify that * the returned int value is strictly less than the length of * {@code list}. * * <p> Due to the inherent race condition in this method, it is recommended * that the method only be used for debugging and monitoring purposes. * * @param list * an array into which to put the list of thread groups * * @param recurse * if {@code true}, recursively enumerate all subgroups * * @return the number of thread groups put into the array * * @throws SecurityException * if {@linkplain #checkAccess checkAccess} determines that * the current thread cannot access this thread group * * @since 1.0 */ public int enumerate(ThreadGroup list[], boolean recurse) { checkAccess(); return enumerate(list, 0, recurse); } private int enumerate(ThreadGroup list[], int n, boolean recurse) { int ngroupsSnapshot = 0; ThreadGroup[] groupsSnapshot = null; synchronized (this) { if (destroyed) { return n; } int ng = ngroups; if (ng > list.length - n) { ng = list.length - n; } if (ng > 0) { System.arraycopy(groups, 0, list, n, ng); n += ng; } if (recurse) { ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } } if (recurse) { for (int i = 0; i < ngroupsSnapshot; i++) { n = groupsSnapshot[i].enumerate(list, n, true); } } return n; } /** * Stops all threads in this thread group. * <p> * First, the {@code checkAccess} method of this thread group is * called with no arguments; this may result in a security exception. * <p> * This method then calls the {@code stop} method on all the * threads in this thread group and in all of its subgroups. * * @throws SecurityException if the current thread is not allowed * to access this thread group or any of the threads in * the thread group. * @see java.lang.SecurityException * @see java.lang.Thread#stop() * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 * @deprecated This method is inherently unsafe. See * {@link Thread#stop} for details. */ @Deprecated(since = "1.2") public final void stop() { if (stopOrSuspend(false)) Thread.currentThread().stop(); } /** * Interrupts all threads in this thread group. * <p> * First, the {@code checkAccess} method of this thread group is * called with no arguments; this may result in a security exception. * <p> * This method then calls the {@code interrupt} method on all the * threads in this thread group and in all of its subgroups. * * @throws SecurityException if the current thread is not allowed * to access this thread group or any of the threads in * the thread group. * @see java.lang.Thread#interrupt() * @see java.lang.SecurityException * @see java.lang.ThreadGroup#checkAccess() * @since 1.2 */ public final void interrupt() { int ngroupsSnapshot; ThreadGroup[] groupsSnapshot; synchronized (this) { checkAccess(); for (int i = 0; i < nthreads; i++) { threads[i].interrupt(); } ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } for (int i = 0; i < ngroupsSnapshot; i++) { groupsSnapshot[i].interrupt(); } } /** * Suspends all threads in this thread group. * <p> * First, the {@code checkAccess} method of this thread group is * called with no arguments; this may result in a security exception. * <p> * This method then calls the {@code suspend} method on all the * threads in this thread group and in all of its subgroups. * * @throws SecurityException if the current thread is not allowed * to access this thread group or any of the threads in * the thread group. * @see java.lang.Thread#suspend() * @see java.lang.SecurityException * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 * @deprecated This method is inherently deadlock-prone. See * {@link Thread#suspend} for details. */ @Deprecated(since = "1.2") @SuppressWarnings("deprecation") public final void suspend() { if (stopOrSuspend(true)) Thread.currentThread().suspend(); } /** * Helper method: recursively stops or suspends (as directed by the * boolean argument) all of the threads in this thread group and its * subgroups, except the current thread. This method returns true * if (and only if) the current thread is found to be in this thread * group or one of its subgroups. */ @SuppressWarnings("deprecation") private boolean stopOrSuspend(boolean suspend) { boolean suicide = false; Thread us = Thread.currentThread(); int ngroupsSnapshot; ThreadGroup[] groupsSnapshot = null; synchronized (this) { checkAccess(); for (int i = 0; i < nthreads; i++) { if (threads[i] == us) suicide = true; else if (suspend) threads[i].suspend(); else threads[i].stop(); } ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } } for (int i = 0; i < ngroupsSnapshot; i++) suicide = groupsSnapshot[i].stopOrSuspend(suspend) || suicide; return suicide; } /** * Resumes all threads in this thread group. * <p> * First, the {@code checkAccess} method of this thread group is * called with no arguments; this may result in a security exception. * <p> * This method then calls the {@code resume} method on all the * threads in this thread group and in all of its sub groups. * * @throws SecurityException if the current thread is not allowed to * access this thread group or any of the threads in the * thread group. * @see java.lang.SecurityException * @see java.lang.Thread#resume() * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 * @deprecated This method is used solely in conjunction with * {@code Thread.suspend} and {@code ThreadGroup.suspend}, * both of which have been deprecated, as they are inherently * deadlock-prone. See {@link Thread#suspend} for details. */ @Deprecated(since = "1.2") @SuppressWarnings("deprecation") public final void resume() { int ngroupsSnapshot; ThreadGroup[] groupsSnapshot; synchronized (this) { checkAccess(); for (int i = 0; i < nthreads; i++) { threads[i].resume(); } ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } for (int i = 0; i < ngroupsSnapshot; i++) { groupsSnapshot[i].resume(); } } /** * Destroys this thread group and all of its subgroups. This thread * group must be empty, indicating that all threads that had been in * this thread group have since stopped. * <p> * First, the {@code checkAccess} method of this thread group is * called with no arguments; this may result in a security exception. * * @throws IllegalThreadStateException if the thread group is not * empty or if the thread group has already been destroyed. * @throws SecurityException if the current thread cannot modify this * thread group. * @see java.lang.ThreadGroup#checkAccess() * @since 1.0 */ public final void destroy() { int ngroupsSnapshot; ThreadGroup[] groupsSnapshot; synchronized (this) { checkAccess(); if (destroyed || (nthreads > 0)) { throw new IllegalThreadStateException(); } ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } if (parent != null) { destroyed = true; ngroups = 0; groups = null; nthreads = 0; threads = null; } } for (int i = 0; i < ngroupsSnapshot; i += 1) { groupsSnapshot[i].destroy(); } if (parent != null) { parent.remove(this); } } /** * Adds the specified Thread group to this group. * @param g the specified Thread group to be added * @throws IllegalThreadStateException If the Thread group has been destroyed. */ private final void add(ThreadGroup g) { synchronized (this) { if (destroyed) { throw new IllegalThreadStateException(); } if (groups == null) { groups = new ThreadGroup[4]; } else if (ngroups == groups.length) { groups = Arrays.copyOf(groups, ngroups * 2); } groups[ngroups] = g; // This is done last so it doesn't matter in case the // thread is killed ngroups++; } } /** * Removes the specified Thread group from this group. * @param g the Thread group to be removed * @return if this Thread has already been destroyed. */ private void remove(ThreadGroup g) { synchronized (this) { if (destroyed) { return; } for (int i = 0; i < ngroups; i++) { if (groups[i] == g) { ngroups -= 1; System.arraycopy(groups, i + 1, groups, i, ngroups - i); // Zap dangling reference to the dead group so that // the garbage collector will collect it. groups[ngroups] = null; break; } } if (nthreads == 0) { notifyAll(); } if (daemon && (nthreads == 0) && (nUnstartedThreads == 0) && (ngroups == 0)) { destroy(); } } } /** * Increments the count of unstarted threads in the thread group. * Unstarted threads are not added to the thread group so that they * can be collected if they are never started, but they must be * counted so that daemon thread groups with unstarted threads in * them are not destroyed. */ void addUnstarted() { synchronized (this) { if (destroyed) { throw new IllegalThreadStateException(); } nUnstartedThreads++; } } /** * Adds the specified thread to this thread group. * * <p> Note: This method is called from both library code * and the Virtual Machine. It is called from VM to add * certain system threads to the system thread group. * * @param t * the Thread to be added * * @throws IllegalThreadStateException * if the Thread group has been destroyed */ void add(Thread t) { synchronized (this) { if (destroyed) { throw new IllegalThreadStateException(); } if (threads == null) { threads = new Thread[4]; } else if (nthreads == threads.length) { threads = Arrays.copyOf(threads, nthreads * 2); } threads[nthreads] = t; // This is done last so it doesn't matter in case the // thread is killed nthreads++; // The thread is now a fully fledged member of the group, even // though it may, or may not, have been started yet. It will prevent // the group from being destroyed so the unstarted Threads count is // decremented. nUnstartedThreads--; } } /** * Notifies the group that the thread {@code t} has failed * an attempt to start. * * <p> The state of this thread group is rolled back as if the * attempt to start the thread has never occurred. The thread is again * considered an unstarted member of the thread group, and a subsequent * attempt to start the thread is permitted. * * @param t * the Thread whose start method was invoked */ void threadStartFailed(Thread t) { synchronized (this) { remove(t); nUnstartedThreads++; } } /** * Notifies the group that the thread {@code t} has terminated. * * <p> Destroy the group if all of the following conditions are * true: this is a daemon thread group; there are no more alive * or unstarted threads in the group; there are no subgroups in * this thread group. * * @param t * the Thread that has terminated */ void threadTerminated(Thread t) { synchronized (this) { remove(t); if (nthreads == 0) { notifyAll(); } if (daemon && (nthreads == 0) && (nUnstartedThreads == 0) && (ngroups == 0)) { destroy(); } } } /** * Removes the specified Thread from this group. Invoking this method * on a thread group that has been destroyed has no effect. * * @param t * the Thread to be removed */ private void remove(Thread t) { synchronized (this) { if (destroyed) { return; } for (int i = 0; i < nthreads; i++) { if (threads[i] == t) { System.arraycopy(threads, i + 1, threads, i, --nthreads - i); // Zap dangling reference to the dead thread so that // the garbage collector will collect it. threads[nthreads] = null; break; } } } } /** * Prints information about this thread group to the standard * output. This method is useful only for debugging. * * @since 1.0 */ public void list() { list(System.out, 0); } void list(PrintStream out, int indent) { int ngroupsSnapshot; ThreadGroup[] groupsSnapshot; synchronized (this) { for (int j = 0; j < indent; j++) { out.print(" "); } out.println(this); indent += 4; for (int i = 0; i < nthreads; i++) { for (int j = 0; j < indent; j++) { out.print(" "); } out.println(threads[i]); } ngroupsSnapshot = ngroups; if (groups != null) { groupsSnapshot = Arrays.copyOf(groups, ngroupsSnapshot); } else { groupsSnapshot = null; } } for (int i = 0; i < ngroupsSnapshot; i++) { groupsSnapshot[i].list(out, indent); } } /** * Called by the Java Virtual Machine when a thread in this * thread group stops because of an uncaught exception, and the thread * does not have a specific {@link Thread.UncaughtExceptionHandler} * installed. * <p> * The {@code uncaughtException} method of * {@code ThreadGroup} does the following: * <ul> * <li>If this thread group has a parent thread group, the * {@code uncaughtException} method of that parent is called * with the same two arguments. * <li>Otherwise, this method checks to see if there is a * {@linkplain Thread#getDefaultUncaughtExceptionHandler default * uncaught exception handler} installed, and if so, its * {@code uncaughtException} method is called with the same * two arguments. * <li>Otherwise, this method determines if the {@code Throwable} * argument is an instance of {@link ThreadDeath}. If so, nothing * special is done. Otherwise, a message containing the * thread's name, as returned from the thread's {@link * Thread#getName getName} method, and a stack backtrace, * using the {@code Throwable}'s {@link * Throwable#printStackTrace printStackTrace} method, is * printed to the {@linkplain System#err standard error stream}. * </ul> * <p> * Applications can override this method in subclasses of * {@code ThreadGroup} to provide alternative handling of * uncaught exceptions. * * @param t the thread that is about to exit. * @param e the uncaught exception. * @since 1.0 */ public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else { Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler(); if (ueh != null) { ueh.uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { System.err.print("Exception in thread \"" + t.getName() + "\" "); e.printStackTrace(System.err); } } } /** * Used by VM to control lowmem implicit suspension. * * @param b boolean to allow or disallow suspension * @return true on success * @since 1.1 * @deprecated The definition of this call depends on {@link #suspend}, * which is deprecated. Further, the behavior of this call * was never specified. */ @Deprecated(since = "1.2") public boolean allowThreadSuspension(boolean b) { return true; } /** * Returns a string representation of this Thread group. * * @return a string representation of this thread group. * @since 1.0 */ public String toString() { return getClass().getName() + "[name=" + getName() + ",maxpri=" + maxPriority + "]"; } }