blob: e99e99ff9cd281414f5cb35b9d3c6e49346e08e1 [file] [log] [blame]
/*
* 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();
}
}