/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java.lang; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import libcore.util.CollectionUtils; /** * {@code ThreadGroup} is a means of organizing threads into a hierarchical structure. * This class is obsolete. See <i>Effective Java</i> Item 73, "Avoid thread groups" for details. * @see Thread */ public class ThreadGroup implements Thread.UncaughtExceptionHandler { // Name of this ThreadGroup // VM needs this field name for debugging. private String name; // Maximum priority for Threads inside this ThreadGroup private int maxPriority = Thread.MAX_PRIORITY; // The ThreadGroup to which this ThreadGroup belongs // VM needs this field name for debugging. final ThreadGroup parent; /** * Weak references to the threads in this group. * Access is guarded by synchronizing on this field. */ private final List<WeakReference<Thread>> threadRefs = new ArrayList<WeakReference<Thread>>(5); /** * View of the threads. * Access is guarded by synchronizing on threadRefs. */ private final Iterable<Thread> threads = CollectionUtils.dereferenceIterable(threadRefs, true); /** * Thread groups. Access is guarded by synchronizing on this field. */ private final List<ThreadGroup> groups = new ArrayList<ThreadGroup>(3); // Whether this ThreadGroup is a daemon ThreadGroup or not private boolean isDaemon; // Whether this ThreadGroup has already been destroyed or not private boolean isDestroyed; /* the VM uses these directly; do not rename */ static final ThreadGroup mSystem = new ThreadGroup(); static final ThreadGroup mMain = new ThreadGroup(mSystem, "main"); /** * Constructs a new {@code ThreadGroup} with the given name. The new {@code ThreadGroup} * will be child of the {@code ThreadGroup} to which the calling thread belongs. * * @param name the name * @see Thread#currentThread */ public ThreadGroup(String name) { this(Thread.currentThread().getThreadGroup(), name); } /** * Constructs a new {@code ThreadGroup} with the given name, as a child of the * given {@code ThreadGroup}. * * @param parent the parent * @param name the name * @throws NullPointerException if {@code parent == null} * @throws IllegalThreadStateException if {@code parent} has been * destroyed already */ public ThreadGroup(ThreadGroup parent, String name) { if (parent == null) { throw new NullPointerException("parent == null"); } this.name = name; this.parent = parent; if (parent != null) { parent.add(this); this.setMaxPriority(parent.getMaxPriority()); if (parent.isDaemon()) { this.setDaemon(true); } } } /** * Initialize the special "system" ThreadGroup. Was "main" in Harmony, * but we have an additional group above that in Android. */ private ThreadGroup() { this.name = "system"; this.parent = null; } /** * Returns the number of running {@code Thread}s which are children of this thread group, * directly or indirectly. * * @return the number of children */ public int activeCount() { int count = 0; synchronized (threadRefs) { for (Thread thread : threads) { if (thread.isAlive()) { count++; } } } synchronized (groups) { for (ThreadGroup group : groups) { count += group.activeCount(); } } return count; } /** * Returns the number of {@code ThreadGroup}s which are children of this group, * directly or indirectly. * * @return the number of children */ public int activeGroupCount() { int count = 0; synchronized (groups) { for (ThreadGroup group : groups) { // One for this group & the subgroups count += 1 + group.activeGroupCount(); } } return count; } /** * Adds a {@code ThreadGroup} to this thread group. * * @param g ThreadGroup to add * @throws IllegalThreadStateException if this group has been destroyed already */ private void add(ThreadGroup g) throws IllegalThreadStateException { synchronized (groups) { if (isDestroyed) { throw new IllegalThreadStateException(); } groups.add(g); } } /** * Does nothing. The definition of this method depends on the deprecated * method {@link #suspend()}. The exact behavior of this call was never * specified. * * @param b Used to control low memory implicit suspension * @return {@code true} (always) * * @deprecated Required deprecated method suspend(). */ @Deprecated public boolean allowThreadSuspension(boolean b) { // Does not apply to this VM, no-op return true; } /** * Does nothing. */ public final void checkAccess() { } /** * Destroys this thread group and recursively all its subgroups. It is only legal * to destroy a {@code ThreadGroup} that has no threads in it. Any daemon * {@code ThreadGroup} is destroyed automatically when it becomes empty (no threads * or thread groups in it). * * @throws IllegalThreadStateException if this thread group or any of its * subgroups has been destroyed already or if it still contains * threads. */ public final void destroy() { synchronized (threadRefs) { synchronized (groups) { if (isDestroyed) { throw new IllegalThreadStateException( "Thread group was already destroyed: " + (this.name != null ? this.name : "n/a")); } if (threads.iterator().hasNext()) { throw new IllegalThreadStateException( "Thread group still contains threads: " + (this.name != null ? this.name : "n/a")); } // Call recursively for subgroups while (!groups.isEmpty()) { // We always get the first element - remember, when the // child dies it removes itself from our collection. See // below. groups.get(0).destroy(); } if (parent != null) { parent.remove(this); } // Now that the ThreadGroup is really destroyed it can be tagged as so this.isDestroyed = true; } } } /* * Auxiliary method that destroys this thread group and recursively all its * subgroups if this is a daemon ThreadGroup. * * @see #destroy * @see #setDaemon * @see #isDaemon */ private void destroyIfEmptyDaemon() { // Has to be non-destroyed daemon to make sense synchronized (threadRefs) { if (isDaemon && !isDestroyed && !threads.iterator().hasNext()) { synchronized (groups) { if (groups.isEmpty()) { destroy(); } } } } } /** * Iterates over all active threads in this group (and its sub-groups) and * stores the threads in the given array. Returns when the array is full or * no more threads remain, whichever happens first. * * <p>Note that this method will silently ignore any threads that don't fit in the * supplied array. * * @param threads the array into which the {@code Thread}s will be copied * @return the number of {@code Thread}s that were copied */ public int enumerate(Thread[] threads) { return enumerate(threads, true); } /** * Iterates over all active threads in this group (and, optionally, its * sub-groups) and stores the threads in the given array. Returns when the * array is full or no more threads remain, whichever happens first. * * <p>Note that this method will silently ignore any threads that don't fit in the * supplied array. * * @param threads the array into which the {@code Thread}s will be copied * @param recurse indicates whether {@code Thread}s in subgroups should be * recursively copied as well * @return the number of {@code Thread}s that were copied */ public int enumerate(Thread[] threads, boolean recurse) { return enumerateGeneric(threads, recurse, 0, true); } /** * Iterates over all thread groups in this group (and its sub-groups) and * and stores the groups in the given array. Returns when the array is full * or no more groups remain, whichever happens first. * * <p>Note that this method will silently ignore any thread groups that don't fit in the * supplied array. * * @param groups the array into which the {@code ThreadGroup}s will be copied * @return the number of {@code ThreadGroup}s that were copied */ public int enumerate(ThreadGroup[] groups) { return enumerate(groups, true); } /** * Iterates over all thread groups in this group (and, optionally, its * sub-groups) and stores the groups in the given array. Returns when * the array is full or no more groups remain, whichever happens first. * * <p>Note that this method will silently ignore any thread groups that don't fit in the * supplied array. * * @param groups the array into which the {@code ThreadGroup}s will be copied * @param recurse indicates whether {@code ThreadGroup}s in subgroups should be * recursively copied as well or not * @return the number of {@code ThreadGroup}s that were copied */ public int enumerate(ThreadGroup[] groups, boolean recurse) { return enumerateGeneric(groups, recurse, 0, false); } /** * Copies into <param>enumeration</param> starting at * <param>enumerationIndex</param> all Threads or ThreadGroups in the * receiver. If <param>recurse</param> is true, recursively enumerate the * elements in subgroups. * * If the array passed as parameter is too small no exception is thrown - * the extra elements are simply not copied. * * @param enumeration array into which the elements will be copied * @param recurse Indicates whether subgroups should be enumerated or not * @param enumerationIndex Indicates in which position of the enumeration * array we are * @param enumeratingThreads Indicates whether we are enumerating Threads or * ThreadGroups * @return How many elements were enumerated/copied over */ private int enumerateGeneric(Object[] enumeration, boolean recurse, int enumerationIndex, boolean enumeratingThreads) { if (enumeratingThreads) { synchronized (threadRefs) { // walk the references directly so we can iterate in reverse order for (int i = threadRefs.size() - 1; i >= 0; --i) { Thread thread = threadRefs.get(i).get(); if (thread != null && thread.isAlive()) { if (enumerationIndex >= enumeration.length) { return enumerationIndex; } enumeration[enumerationIndex++] = thread; } } } } else { synchronized (groups) { for (int i = groups.size() - 1; i >= 0; --i) { if (enumerationIndex >= enumeration.length) { return enumerationIndex; } enumeration[enumerationIndex++] = groups.get(i); } } } if (recurse) { synchronized (groups) { for (ThreadGroup group : groups) { if (enumerationIndex >= enumeration.length) { return enumerationIndex; } enumerationIndex = group.enumerateGeneric(enumeration, recurse, enumerationIndex, enumeratingThreads); } } } return enumerationIndex; } /** * Returns the maximum allowed priority for a {@code Thread} in this thread group. * * @return the maximum priority * * @see #setMaxPriority */ public final int getMaxPriority() { return maxPriority; } /** * Returns the name of this thread group. * * @return the group's name */ public final String getName() { return name; } /** * Returns this thread group's parent {@code ThreadGroup}. It can be null if this * is the the root ThreadGroup. * * @return the parent */ public final ThreadGroup getParent() { return parent; } /** * Interrupts every {@code Thread} in this group and recursively in all its * subgroups. * * @see Thread#interrupt */ public final void interrupt() { synchronized (threadRefs) { for (Thread thread : threads) { thread.interrupt(); } } synchronized (groups) { for (ThreadGroup group : groups) { group.interrupt(); } } } /** * Checks whether this thread group is a daemon {@code ThreadGroup}. * * @return true if this thread group is a daemon {@code ThreadGroup} * * @see #setDaemon * @see #destroy */ public final boolean isDaemon() { return isDaemon; } /** * Checks whether this thread group has already been destroyed. * * @return true if this thread group has already been destroyed * @see #destroy */ public synchronized boolean isDestroyed() { return isDestroyed; } /** * Outputs to {@code System.out} a text representation of the * hierarchy of {@code Thread}s and {@code ThreadGroup}s in this thread group (and recursively). * Proper indentation is used to show the nesting of groups inside groups * and threads inside groups. */ public void list() { // We start in a fresh line System.out.println(); list(0); } /* * Outputs to {@code System.out}a text representation of the * hierarchy of Threads and ThreadGroups in this thread group (and recursively). * The indentation will be four spaces per level of nesting. * * @param levels How many levels of nesting, so that proper indentation can * be output. */ private void list(int levels) { indent(levels); System.out.println(this.toString()); ++levels; synchronized (threadRefs) { for (Thread thread : threads) { indent(levels); System.out.println(thread); } } synchronized (groups) { for (ThreadGroup group : groups) { group.list(levels); } } } private void indent(int levels) { for (int i = 0; i < levels; i++) { System.out.print(" "); // 4 spaces for each level } } /** * Checks whether this thread group is a direct or indirect parent group of a * given {@code ThreadGroup}. * * @param g the potential child {@code ThreadGroup} * @return true if this thread group is parent of {@code g} */ public final boolean parentOf(ThreadGroup g) { while (g != null) { if (this == g) { return true; } g = g.parent; } return false; } /** * Removes an immediate subgroup. * * @param g ThreadGroup to remove * * @see #add(Thread) * @see #add(ThreadGroup) */ private void remove(ThreadGroup g) { synchronized (groups) { for (Iterator<ThreadGroup> i = groups.iterator(); i.hasNext(); ) { ThreadGroup threadGroup = i.next(); if (threadGroup.equals(g)) { i.remove(); break; } } } destroyIfEmptyDaemon(); } /** * Resumes every thread in this group and recursively in all its * subgroups. * * @see Thread#resume * @see #suspend * * @deprecated Requires deprecated method Thread.resume(). */ @SuppressWarnings("deprecation") @Deprecated public final void resume() { synchronized (threadRefs) { for (Thread thread : threads) { thread.resume(); } } synchronized (groups) { for (ThreadGroup group : groups) { group.resume(); } } } /** * Sets whether this is a daemon {@code ThreadGroup} or not. Daemon * thread groups are automatically destroyed when they become empty. * * @param isDaemon the new value * @see #isDaemon * @see #destroy */ public final void setDaemon(boolean isDaemon) { this.isDaemon = isDaemon; } /** * Configures the maximum allowed priority for a {@code Thread} in this group and * recursively in all its subgroups. * * <p>A caller can never increase the maximum priority of a thread group. * Such an attempt will not result in an exception, it will * simply leave the thread group with its current maximum priority. * * @param newMax the new maximum priority to be set * * @throws IllegalArgumentException if the new priority is greater than * Thread.MAX_PRIORITY or less than Thread.MIN_PRIORITY * * @see #getMaxPriority */ public final void setMaxPriority(int newMax) { if (newMax <= this.maxPriority) { if (newMax < Thread.MIN_PRIORITY) { newMax = Thread.MIN_PRIORITY; } int parentPriority = parent == null ? newMax : parent.getMaxPriority(); this.maxPriority = parentPriority <= newMax ? parentPriority : newMax; synchronized (groups) { for (ThreadGroup group : groups) { group.setMaxPriority(newMax); } } } } /** * Stops every thread in this group and recursively in all its subgroups. * * @see Thread#stop() * @see Thread#stop(Throwable) * @see ThreadDeath * * @deprecated Requires deprecated method Thread.stop(). */ @SuppressWarnings("deprecation") @Deprecated public final void stop() { if (stopHelper()) { Thread.currentThread().stop(); } } @SuppressWarnings("deprecation") private boolean stopHelper() { boolean stopCurrent = false; synchronized (threadRefs) { Thread current = Thread.currentThread(); for (Thread thread : threads) { if (thread == current) { stopCurrent = true; } else { thread.stop(); } } } synchronized (groups) { for (ThreadGroup group : groups) { stopCurrent |= group.stopHelper(); } } return stopCurrent; } /** * Suspends every thread in this group and recursively in all its * subgroups. * * @see Thread#suspend * @see #resume * * @deprecated Requires deprecated method Thread.suspend(). */ @SuppressWarnings("deprecation") @Deprecated public final void suspend() { if (suspendHelper()) { Thread.currentThread().suspend(); } } @SuppressWarnings("deprecation") private boolean suspendHelper() { boolean suspendCurrent = false; synchronized (threadRefs) { Thread current = Thread.currentThread(); for (Thread thread : threads) { if (thread == current) { suspendCurrent = true; } else { thread.suspend(); } } } synchronized (groups) { for (ThreadGroup group : groups) { suspendCurrent |= group.suspendHelper(); } } return suspendCurrent; } @Override public String toString() { return getClass().getName() + "[name=" + getName() + ",maxPriority=" + getMaxPriority() + "]"; } /** * Handles uncaught exceptions. Any uncaught exception in any {@code Thread} * is forwarded to the thread's {@code ThreadGroup} by invoking this * method. * * <p>New code should use {@link Thread#setUncaughtExceptionHandler} instead of thread groups. * * @param t the Thread that terminated with an uncaught exception * @param e the uncaught exception itself */ public void uncaughtException(Thread t, Throwable e) { if (parent != null) { parent.uncaughtException(t, e); } else if (Thread.getDefaultUncaughtExceptionHandler() != null) { // TODO The spec is unclear regarding this. What do we do? Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e); } else if (!(e instanceof ThreadDeath)) { // No parent group, has to be 'system' Thread Group e.printStackTrace(System.err); } } /** * Called by the Thread constructor. */ final void addThread(Thread thread) throws IllegalThreadStateException { synchronized (threadRefs) { if (isDestroyed) { throw new IllegalThreadStateException(); } threadRefs.add(new WeakReference<Thread>(thread)); } } /** * Called by the VM when a Thread dies. */ final void removeThread(Thread thread) throws IllegalThreadStateException { synchronized (threadRefs) { for (Iterator<Thread> i = threads.iterator(); i.hasNext(); ) { if (i.next().equals(thread)) { i.remove(); break; } } } destroyIfEmptyDaemon(); } }