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      */
ThreadGroup(String name)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      */
ThreadGroup(ThreadGroup parent, String name)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      */
ThreadGroup()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      */
activeCount()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      */
activeGroupCount()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      */
add(ThreadGroup g)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
allowThreadSuspension(boolean b)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      */
checkAccess()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      */
destroy()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      */
destroyIfEmptyDaemon()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      */
enumerate(Thread[] threads)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      */
enumerate(Thread[] threads, boolean recurse)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      */
enumerate(ThreadGroup[] groups)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      */
enumerate(ThreadGroup[] groups, boolean recurse)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      */
enumerateGeneric(Object[] enumeration, boolean recurse, int enumerationIndex, boolean enumeratingThreads)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      */
getMaxPriority()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      */
getName()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      */
getParent()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      */
interrupt()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      */
isDaemon()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      */
isDestroyed()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      */
list()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      */
list(int levels)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 
indent(int levels)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      */
parentOf(ThreadGroup g)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      */
remove(ThreadGroup g)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
resume()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      */
setDaemon(boolean isDaemon)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      */
setMaxPriority(int newMax)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
stop()607     public final void stop() {
608         if (stopHelper()) {
609             Thread.currentThread().stop();
610         }
611     }
612 
613     @SuppressWarnings("deprecation")
stopHelper()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
suspend()645     public final void suspend() {
646         if (suspendHelper()) {
647             Thread.currentThread().suspend();
648         }
649     }
650 
651     @SuppressWarnings("deprecation")
suspendHelper()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
toString()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      */
uncaughtException(Thread t, Throwable e)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      */
addThread(Thread thread)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      */
removeThread(Thread thread)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