Home | History | Annotate | Download | only in lang
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package java.lang;
     19 
     20 import java.lang.ref.WeakReference;
     21 import java.util.ArrayList;
     22 import java.util.Iterator;
     23 import java.util.List;
     24 import libcore.util.CollectionUtils;
     25 
     26 /**
     27  * {@code ThreadGroup} is a means of organizing threads into a hierarchical structure.
     28  * This class is obsolete. See <i>Effective Java</i> Item 73, "Avoid thread groups" for details.
     29  * @see Thread
     30  */
     31 public class ThreadGroup implements Thread.UncaughtExceptionHandler {
     32 
     33     // Name of this ThreadGroup
     34     // VM needs this field name for debugging.
     35     private String name;
     36 
     37     // Maximum priority for Threads inside this ThreadGroup
     38     private int maxPriority = Thread.MAX_PRIORITY;
     39 
     40     // The ThreadGroup to which this ThreadGroup belongs
     41     // VM needs this field name for debugging.
     42     final ThreadGroup parent;
     43 
     44     /**
     45      * Weak references to the threads in this group.
     46      * Access is guarded by synchronizing on this field.
     47      */
     48     private final List<WeakReference<Thread>> threadRefs = new ArrayList<WeakReference<Thread>>(5);
     49 
     50     /**
     51      * View of the threads.
     52      * Access is guarded by synchronizing on threadRefs.
     53      */
     54     private final Iterable<Thread> threads = CollectionUtils.dereferenceIterable(threadRefs, true);
     55 
     56     /**
     57      * Thread groups. Access is guarded by synchronizing on this field.
     58      */
     59     private final List<ThreadGroup> groups = new ArrayList<ThreadGroup>(3);
     60 
     61     // Whether this ThreadGroup is a daemon ThreadGroup or not
     62     private boolean isDaemon;
     63 
     64     // Whether this ThreadGroup has already been destroyed or not
     65     private boolean isDestroyed;
     66 
     67     /* the VM uses these directly; do not rename */
     68     static final ThreadGroup systemThreadGroup = new ThreadGroup();
     69     static final ThreadGroup mainThreadGroup = new ThreadGroup(systemThreadGroup, "main");
     70 
     71     /**
     72      * Constructs a new {@code ThreadGroup} with the given name. The new {@code ThreadGroup}
     73      * will be child of the {@code ThreadGroup} to which the calling thread belongs.
     74      *
     75      * @param name the name
     76      * @see Thread#currentThread
     77      */
     78     public ThreadGroup(String name) {
     79         this(Thread.currentThread().getThreadGroup(), name);
     80     }
     81 
     82     /**
     83      * Constructs a new {@code ThreadGroup} with the given name, as a child of the
     84      * given {@code ThreadGroup}.
     85      *
     86      * @param parent the parent
     87      * @param name the name
     88      * @throws NullPointerException if {@code parent == null}
     89      * @throws IllegalThreadStateException if {@code parent} has been
     90      *         destroyed already
     91      */
     92     public ThreadGroup(ThreadGroup parent, String name) {
     93         if (parent == null) {
     94             throw new NullPointerException("parent == null");
     95         }
     96         this.name = name;
     97         this.parent = parent;
     98         if (parent != null) {
     99             parent.add(this);
    100             this.setMaxPriority(parent.getMaxPriority());
    101             if (parent.isDaemon()) {
    102                 this.setDaemon(true);
    103             }
    104         }
    105     }
    106 
    107     /**
    108      * Initialize the special "system" ThreadGroup. Was "main" in Harmony,
    109      * but we have an additional group above that in Android.
    110      */
    111     private ThreadGroup() {
    112         this.name = "system";
    113         this.parent = null;
    114     }
    115 
    116     /**
    117      * Returns the number of running {@code Thread}s which are children of this thread group,
    118      * directly or indirectly.
    119      *
    120      * @return the number of children
    121      */
    122     public int activeCount() {
    123         int count = 0;
    124         synchronized (threadRefs) {
    125             for (Thread thread : threads) {
    126                 if (thread.isAlive()) {
    127                     count++;
    128                 }
    129             }
    130         }
    131         synchronized (groups) {
    132             for (ThreadGroup group : groups) {
    133                 count += group.activeCount();
    134             }
    135         }
    136         return count;
    137     }
    138 
    139     /**
    140      * Returns the number of {@code ThreadGroup}s which are children of this group,
    141      * directly or indirectly.
    142      *
    143      * @return the number of children
    144      */
    145     public int activeGroupCount() {
    146         int count = 0;
    147         synchronized (groups) {
    148             for (ThreadGroup group : groups) {
    149                 // One for this group & the subgroups
    150                 count += 1 + group.activeGroupCount();
    151             }
    152         }
    153         return count;
    154     }
    155 
    156     /**
    157      * Adds a {@code ThreadGroup} to this thread group.
    158      *
    159      * @param g ThreadGroup to add
    160      * @throws IllegalThreadStateException if this group has been destroyed already
    161      */
    162     private void add(ThreadGroup g) throws IllegalThreadStateException {
    163         synchronized (groups) {
    164             if (isDestroyed) {
    165                 throw new IllegalThreadStateException();
    166             }
    167             groups.add(g);
    168         }
    169     }
    170 
    171     /**
    172      * Does nothing. The definition of this method depends on the deprecated
    173      * method {@link #suspend()}. The exact behavior of this call was never
    174      * specified.
    175      *
    176      * @param b Used to control low memory implicit suspension
    177      * @return {@code true} (always)
    178      *
    179      * @deprecated Required deprecated method suspend().
    180      */
    181     @Deprecated
    182     public boolean allowThreadSuspension(boolean b) {
    183         // Does not apply to this VM, no-op
    184         return true;
    185     }
    186 
    187     /**
    188      * Does nothing.
    189      */
    190     public final void checkAccess() {
    191     }
    192 
    193     /**
    194      * Destroys this thread group and recursively all its subgroups. It is only legal
    195      * to destroy a {@code ThreadGroup} that has no threads in it. Any daemon
    196      * {@code ThreadGroup} is destroyed automatically when it becomes empty (no threads
    197      * or thread groups in it).
    198      *
    199      * @throws IllegalThreadStateException if this thread group or any of its
    200      *         subgroups has been destroyed already or if it still contains
    201      *         threads.
    202      */
    203     public final void destroy() {
    204         synchronized (threadRefs) {
    205             synchronized (groups) {
    206                 if (isDestroyed) {
    207                     throw new IllegalThreadStateException(
    208                             "Thread group was already destroyed: "
    209                             + (this.name != null ? this.name : "n/a"));
    210                 }
    211                 if (threads.iterator().hasNext()) {
    212                     throw new IllegalThreadStateException(
    213                             "Thread group still contains threads: "
    214                             + (this.name != null ? this.name : "n/a"));
    215                 }
    216                 // Call recursively for subgroups
    217                 while (!groups.isEmpty()) {
    218                     // We always get the first element - remember, when the
    219                     // child dies it removes itself from our collection. See
    220                     // below.
    221                     groups.get(0).destroy();
    222                 }
    223 
    224                 if (parent != null) {
    225                     parent.remove(this);
    226                 }
    227 
    228                 // Now that the ThreadGroup is really destroyed it can be tagged as so
    229                 this.isDestroyed = true;
    230             }
    231         }
    232     }
    233 
    234     /*
    235      * Auxiliary method that destroys this thread group and recursively all its
    236      * subgroups if this is a daemon ThreadGroup.
    237      *
    238      * @see #destroy
    239      * @see #setDaemon
    240      * @see #isDaemon
    241      */
    242     private void destroyIfEmptyDaemon() {
    243         // Has to be non-destroyed daemon to make sense
    244         synchronized (threadRefs) {
    245             if (isDaemon && !isDestroyed && !threads.iterator().hasNext()) {
    246                 synchronized (groups) {
    247                     if (groups.isEmpty()) {
    248                         destroy();
    249                     }
    250                 }
    251             }
    252         }
    253     }
    254 
    255     /**
    256      * Iterates over all active threads in this group (and its sub-groups) and
    257      * stores the threads in the given array. Returns when the array is full or
    258      * no more threads remain, whichever happens first.
    259      *
    260      * <p>Note that this method will silently ignore any threads that don't fit in the
    261      * supplied array.
    262      *
    263      * @param threads the array into which the {@code Thread}s will be copied
    264      * @return the number of {@code Thread}s that were copied
    265      */
    266     public int enumerate(Thread[] threads) {
    267         return enumerate(threads, true);
    268     }
    269 
    270     /**
    271      * Iterates over all active threads in this group (and, optionally, its
    272      * sub-groups) and stores the threads in the given array. Returns when the
    273      * array is full or no more threads remain, whichever happens first.
    274      *
    275      * <p>Note that this method will silently ignore any threads that don't fit in the
    276      * supplied array.
    277      *
    278      * @param threads the array into which the {@code Thread}s will be copied
    279      * @param recurse indicates whether {@code Thread}s in subgroups should be
    280      *        recursively copied as well
    281      * @return the number of {@code Thread}s that were copied
    282      */
    283     public int enumerate(Thread[] threads, boolean recurse) {
    284         return enumerateGeneric(threads, recurse, 0, true);
    285     }
    286 
    287     /**
    288      * Iterates over all thread groups in this group (and its sub-groups) and
    289      * and stores the groups in the given array. Returns when the array is full
    290      * or no more groups remain, whichever happens first.
    291      *
    292      * <p>Note that this method will silently ignore any thread groups that don't fit in the
    293      * supplied array.
    294      *
    295      * @param groups the array into which the {@code ThreadGroup}s will be copied
    296      * @return the number of {@code ThreadGroup}s that were copied
    297      */
    298     public int enumerate(ThreadGroup[] groups) {
    299         return enumerate(groups, true);
    300     }
    301 
    302     /**
    303      * Iterates over all thread groups in this group (and, optionally, its
    304      * sub-groups) and stores the groups in the given array. Returns when
    305      * the array is full or no more groups remain, whichever happens first.
    306      *
    307      * <p>Note that this method will silently ignore any thread groups that don't fit in the
    308      * supplied array.
    309      *
    310      * @param groups the array into which the {@code ThreadGroup}s will be copied
    311      * @param recurse indicates whether {@code ThreadGroup}s in subgroups should be
    312      *        recursively copied as well or not
    313      * @return the number of {@code ThreadGroup}s that were copied
    314      */
    315     public int enumerate(ThreadGroup[] groups, boolean recurse) {
    316         return enumerateGeneric(groups, recurse, 0, false);
    317     }
    318 
    319     /**
    320      * Copies into <param>enumeration</param> starting at
    321      * <param>enumerationIndex</param> all Threads or ThreadGroups in the
    322      * receiver. If <param>recurse</param> is true, recursively enumerate the
    323      * elements in subgroups.
    324      *
    325      * If the array passed as parameter is too small no exception is thrown -
    326      * the extra elements are simply not copied.
    327      *
    328      * @param enumeration array into which the elements will be copied
    329      * @param recurse Indicates whether subgroups should be enumerated or not
    330      * @param enumerationIndex Indicates in which position of the enumeration
    331      *        array we are
    332      * @param enumeratingThreads Indicates whether we are enumerating Threads or
    333      *        ThreadGroups
    334      * @return How many elements were enumerated/copied over
    335      */
    336     private int enumerateGeneric(Object[] enumeration, boolean recurse, int enumerationIndex,
    337             boolean enumeratingThreads) {
    338         if (enumeratingThreads) {
    339             synchronized (threadRefs) {
    340                 // walk the references directly so we can iterate in reverse order
    341                 for (int i = threadRefs.size() - 1; i >= 0; --i) {
    342                     Thread thread = threadRefs.get(i).get();
    343                     if (thread != null && thread.isAlive()) {
    344                         if (enumerationIndex >= enumeration.length) {
    345                             return enumerationIndex;
    346                         }
    347                         enumeration[enumerationIndex++] = thread;
    348                     }
    349                 }
    350             }
    351         } else {
    352             synchronized (groups) {
    353                 for (int i = groups.size() - 1; i >= 0; --i) {
    354                     if (enumerationIndex >= enumeration.length) {
    355                         return enumerationIndex;
    356                     }
    357                     enumeration[enumerationIndex++] = groups.get(i);
    358                 }
    359             }
    360         }
    361 
    362         if (recurse) {
    363             synchronized (groups) {
    364                 for (ThreadGroup group : groups) {
    365                     if (enumerationIndex >= enumeration.length) {
    366                         return enumerationIndex;
    367                     }
    368                     enumerationIndex = group.enumerateGeneric(enumeration, recurse,
    369                             enumerationIndex, enumeratingThreads);
    370                 }
    371             }
    372         }
    373         return enumerationIndex;
    374     }
    375 
    376     /**
    377      * Returns the maximum allowed priority for a {@code Thread} in this thread group.
    378      *
    379      * @return the maximum priority
    380      *
    381      * @see #setMaxPriority
    382      */
    383     public final int getMaxPriority() {
    384         return maxPriority;
    385     }
    386 
    387     /**
    388      * Returns the name of this thread group.
    389      *
    390      * @return the group's name
    391      */
    392     public final String getName() {
    393         return name;
    394     }
    395 
    396     /**
    397      * Returns this thread group's parent {@code ThreadGroup}. It can be null if this
    398      * is the the root ThreadGroup.
    399      *
    400      * @return the parent
    401      */
    402     public final ThreadGroup getParent() {
    403         return parent;
    404     }
    405 
    406     /**
    407      * Interrupts every {@code Thread} in this group and recursively in all its
    408      * subgroups.
    409      *
    410      * @see Thread#interrupt
    411      */
    412     public final void interrupt() {
    413         synchronized (threadRefs) {
    414             for (Thread thread : threads) {
    415                 thread.interrupt();
    416             }
    417         }
    418         synchronized (groups) {
    419             for (ThreadGroup group : groups) {
    420                 group.interrupt();
    421             }
    422         }
    423     }
    424 
    425     /**
    426      * Checks whether this thread group is a daemon {@code ThreadGroup}.
    427      *
    428      * @return true if this thread group is a daemon {@code ThreadGroup}
    429      *
    430      * @see #setDaemon
    431      * @see #destroy
    432      */
    433     public final boolean isDaemon() {
    434         return isDaemon;
    435     }
    436 
    437     /**
    438      * Checks whether this thread group has already been destroyed.
    439      *
    440      * @return true if this thread group has already been destroyed
    441      * @see #destroy
    442      */
    443     public synchronized boolean isDestroyed() {
    444         return isDestroyed;
    445     }
    446 
    447     /**
    448      * Outputs to {@code System.out} a text representation of the
    449      * hierarchy of {@code Thread}s and {@code ThreadGroup}s in this thread group (and recursively).
    450      * Proper indentation is used to show the nesting of groups inside groups
    451      * and threads inside groups.
    452      */
    453     public void list() {
    454         // We start in a fresh line
    455         System.out.println();
    456         list(0);
    457     }
    458 
    459     /*
    460      * Outputs to {@code System.out}a text representation of the
    461      * hierarchy of Threads and ThreadGroups in this thread group (and recursively).
    462      * The indentation will be four spaces per level of nesting.
    463      *
    464      * @param levels How many levels of nesting, so that proper indentation can
    465      * be output.
    466      */
    467     private void list(int levels) {
    468         indent(levels);
    469         System.out.println(this.toString());
    470 
    471         ++levels;
    472         synchronized (threadRefs) {
    473             for (Thread thread : threads) {
    474                 indent(levels);
    475                 System.out.println(thread);
    476             }
    477         }
    478         synchronized (groups) {
    479             for (ThreadGroup group : groups) {
    480                 group.list(levels);
    481             }
    482         }
    483     }
    484 
    485     private void indent(int levels) {
    486         for (int i = 0; i < levels; i++) {
    487             System.out.print("    "); // 4 spaces for each level
    488         }
    489     }
    490 
    491     /**
    492      * Checks whether this thread group is a direct or indirect parent group of a
    493      * given {@code ThreadGroup}.
    494      *
    495      * @param g the potential child {@code ThreadGroup}
    496      * @return true if this thread group is parent of {@code g}
    497      */
    498     public final boolean parentOf(ThreadGroup g) {
    499         while (g != null) {
    500             if (this == g) {
    501                 return true;
    502             }
    503             g = g.parent;
    504         }
    505         return false;
    506     }
    507 
    508     /**
    509      * Removes an immediate subgroup.
    510      *
    511      * @param g ThreadGroup to remove
    512      *
    513      * @see #add(Thread)
    514      * @see #add(ThreadGroup)
    515      */
    516     private void remove(ThreadGroup g) {
    517         synchronized (groups) {
    518             for (Iterator<ThreadGroup> i = groups.iterator(); i.hasNext(); ) {
    519                 ThreadGroup threadGroup = i.next();
    520                 if (threadGroup.equals(g)) {
    521                     i.remove();
    522                     break;
    523                 }
    524             }
    525         }
    526         destroyIfEmptyDaemon();
    527     }
    528 
    529     /**
    530      * Resumes every thread in this group and recursively in all its
    531      * subgroups.
    532      *
    533      * @see Thread#resume
    534      * @see #suspend
    535      *
    536      * @deprecated Requires deprecated method Thread.resume().
    537      */
    538     @SuppressWarnings("deprecation")
    539     @Deprecated
    540     public final void resume() {
    541         synchronized (threadRefs) {
    542             for (Thread thread : threads) {
    543                 thread.resume();
    544             }
    545         }
    546         synchronized (groups) {
    547             for (ThreadGroup group : groups) {
    548                 group.resume();
    549             }
    550         }
    551     }
    552 
    553     /**
    554      * Sets whether this is a daemon {@code ThreadGroup} or not. Daemon
    555      * thread groups are automatically destroyed when they become empty.
    556      *
    557      * @param isDaemon the new value
    558      * @see #isDaemon
    559      * @see #destroy
    560      */
    561     public final void setDaemon(boolean isDaemon) {
    562         this.isDaemon = isDaemon;
    563     }
    564 
    565     /**
    566      * Configures the maximum allowed priority for a {@code Thread} in this group and
    567      * recursively in all its subgroups.
    568      *
    569      * <p>A caller can never increase the maximum priority of a thread group.
    570      * Such an attempt will not result in an exception, it will
    571      * simply leave the thread group with its current maximum priority.
    572      *
    573      * @param newMax the new maximum priority to be set
    574      *
    575      * @throws IllegalArgumentException if the new priority is greater than
    576      *         Thread.MAX_PRIORITY or less than Thread.MIN_PRIORITY
    577      *
    578      * @see #getMaxPriority
    579      */
    580     public final void setMaxPriority(int newMax) {
    581         if (newMax <= this.maxPriority) {
    582             if (newMax < Thread.MIN_PRIORITY) {
    583                 newMax = Thread.MIN_PRIORITY;
    584             }
    585 
    586             int parentPriority = parent == null ? newMax : parent.getMaxPriority();
    587             this.maxPriority = parentPriority <= newMax ? parentPriority : newMax;
    588             synchronized (groups) {
    589                 for (ThreadGroup group : groups) {
    590                     group.setMaxPriority(newMax);
    591                 }
    592             }
    593         }
    594     }
    595 
    596     /**
    597      * Stops every thread in this group and recursively in all its subgroups.
    598      *
    599      * @see Thread#stop()
    600      * @see Thread#stop(Throwable)
    601      * @see ThreadDeath
    602      *
    603      * @deprecated Requires deprecated method Thread.stop().
    604      */
    605     @SuppressWarnings("deprecation")
    606     @Deprecated
    607     public final void stop() {
    608         if (stopHelper()) {
    609             Thread.currentThread().stop();
    610         }
    611     }
    612 
    613     @SuppressWarnings("deprecation")
    614     private boolean stopHelper() {
    615         boolean stopCurrent = false;
    616         synchronized (threadRefs) {
    617             Thread current = Thread.currentThread();
    618             for (Thread thread : threads) {
    619                 if (thread == current) {
    620                     stopCurrent = true;
    621                 } else {
    622                     thread.stop();
    623                 }
    624             }
    625         }
    626         synchronized (groups) {
    627             for (ThreadGroup group : groups) {
    628                 stopCurrent |= group.stopHelper();
    629             }
    630         }
    631         return stopCurrent;
    632     }
    633 
    634     /**
    635      * Suspends every thread in this group and recursively in all its
    636      * subgroups.
    637      *
    638      * @see Thread#suspend
    639      * @see #resume
    640      *
    641      * @deprecated Requires deprecated method Thread.suspend().
    642      */
    643     @SuppressWarnings("deprecation")
    644     @Deprecated
    645     public final void suspend() {
    646         if (suspendHelper()) {
    647             Thread.currentThread().suspend();
    648         }
    649     }
    650 
    651     @SuppressWarnings("deprecation")
    652     private boolean suspendHelper() {
    653         boolean suspendCurrent = false;
    654         synchronized (threadRefs) {
    655             Thread current = Thread.currentThread();
    656             for (Thread thread : threads) {
    657                 if (thread == current) {
    658                     suspendCurrent = true;
    659                 } else {
    660                     thread.suspend();
    661                 }
    662             }
    663         }
    664         synchronized (groups) {
    665             for (ThreadGroup group : groups) {
    666                 suspendCurrent |= group.suspendHelper();
    667             }
    668         }
    669         return suspendCurrent;
    670     }
    671 
    672     @Override
    673     public String toString() {
    674         return getClass().getName() + "[name=" + getName()
    675                 + ",maxPriority=" + getMaxPriority() + "]";
    676     }
    677 
    678     /**
    679      * Handles uncaught exceptions. Any uncaught exception in any {@code Thread}
    680      * is forwarded to the thread's {@code ThreadGroup} by invoking this
    681      * method.
    682      *
    683      * <p>New code should use {@link Thread#setUncaughtExceptionHandler} instead of thread groups.
    684      *
    685      * @param t the Thread that terminated with an uncaught exception
    686      * @param e the uncaught exception itself
    687      */
    688     public void uncaughtException(Thread t, Throwable e) {
    689         if (parent != null) {
    690             parent.uncaughtException(t, e);
    691         } else if (Thread.getDefaultUncaughtExceptionHandler() != null) {
    692             // TODO The spec is unclear regarding this. What do we do?
    693             Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
    694         } else if (!(e instanceof ThreadDeath)) {
    695             // No parent group, has to be 'system' Thread Group
    696             e.printStackTrace(System.err);
    697         }
    698     }
    699 
    700     /**
    701      * Called by the Thread constructor.
    702      */
    703     final void addThread(Thread thread) throws IllegalThreadStateException {
    704         synchronized (threadRefs) {
    705             if (isDestroyed) {
    706                 throw new IllegalThreadStateException();
    707             }
    708             threadRefs.add(new WeakReference<Thread>(thread));
    709         }
    710     }
    711 
    712     /**
    713      * Called by the VM when a Thread dies.
    714      */
    715     final void removeThread(Thread thread) throws IllegalThreadStateException {
    716         synchronized (threadRefs) {
    717             for (Iterator<Thread> i = threads.iterator(); i.hasNext(); ) {
    718                 if (i.next().equals(thread)) {
    719                     i.remove();
    720                     break;
    721                 }
    722             }
    723         }
    724         destroyIfEmptyDaemon();
    725     }
    726 }
    727