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