Back to Java
2026-03-218 min read

Java - Thread Group

Learn Java - Thread Group step by step with clear examples and exercises.

Why This Matters

The ThreadGroup class plays a crucial role in managing and coordinating multiple threads within a Java application. By organizing threads into a hierarchical structure, it becomes easier to monitor their execution, handle exceptions, and avoid deadlocks or race conditions. Understanding the ThreadGroup class can help you write more efficient, robust, and scalable multi-threaded applications.

Prerequisites

Before diving into the ThreadGroup class, it is essential to have a good understanding of:

  1. Java basics, such as variables, data types, operators, loops, and control structures.
  2. Object-oriented programming concepts like classes, objects, inheritance, polymorphism, and interfaces.
  3. The Thread class in Java for creating and managing threads.
  4. Exception handling using the try, catch, and finally blocks.
  5. Understanding the importance of multi-threading and common issues such as deadlocks and race conditions.

Core Concept

The ThreadGroup class, located in the java.lang package, represents a group of threads. Each thread belongs to exactly one group, which can be either the main group or another user-created group. The main thread always belongs to the main group, which has an empty name and is the parent of all other groups.

Creating a ThreadGroup

To create a new ThreadGroup, you can use its constructor that takes two arguments:

  1. A String argument representing the name of the group. If not provided, it will be null.
  2. A ThreadGroup argument representing the parent group of the newly created group. If not provided, the new group will become a direct child of the main group.

Here's an example:

ThreadGroup myThreadGroup = new ThreadGroup("My Group");

In this example, myThreadGroup is a new thread group with the name "My Group". If you want to create a child group of an existing group, pass that group as the second argument:

ThreadGroup parentGroup = new ThreadGroup("Parent Group");
ThreadGroup childGroup = new ThreadGroup(parentGroup, "Child Group");

In this example, childGroup is a child thread group of parentGroup.

Methods in ThreadGroup

The ThreadGroup class provides several methods to manage and monitor threads within the group:

  1. activeCount(): Returns the number of live threads (those that have started but not yet terminated) in this group and all its subgroups.
  2. enumeration(): Returns an enumeration of the threads in this group, including threads in all its subgroups.
  3. currentThread(): Returns a reference to the currently executing thread as a Thread object.
  4. interrupted(): Tests if the current thread has been interrupted.
  5. uncaughtException(): Returns an UncaughtExceptionHandler for this group, which is responsible for handling any uncaught exceptions in threads within this group.
  6. destroy(): Causes the termination of all threads in this group and its subgroups.
  7. setDaemon(boolean value): Sets whether the specified thread (or all threads in the case of a group) is a daemon thread. Daemon threads are low-priority background threads that run only as long as there are user-initiated threads still running.
  8. getParent(): Returns the parent ThreadGroup of this group, or null if it's the main group.
  9. getName(): Returns the name of the thread group as a string.
  10. setName(String name): Sets the name of the thread group.

Worked Example

Let's create a simple example that demonstrates using the ThreadGroup class to manage multiple threads. We will create two groups, a parent and a child, and create three threads within each group:

import java.lang.Thread;
import java.lang.ThreadGroup;
import java.util.Enumeration;

public class ThreadGroupExample {
public static void main(String[] args) throws InterruptedException {
// Create parent thread group
ThreadGroup parentGroup = new ThreadGroup("Parent Group");

// Create child thread group
ThreadGroup childGroup = new ThreadGroup(parentGroup, "Child Group");

// Create three threads in the parent group
Runnable runnable1 = () -> System.out.println("Thread 1 (Parent) started.");
Thread thread1 = new Thread(runnable1, "Thread 1 - Parent", 5);
thread1.start();

Runnable runnable2 = () -> System.out.println("Thread 2 (Parent) started.");
Thread thread2 = new Thread(runnable2, "Thread 2 - Parent", 5);
thread2.start();

Runnable runnable3 = () -> System.out.println("Thread 3 (Parent) started.");
Thread thread3 = new Thread(runnable3, "Thread 3 - Parent", 5);
thread3.start();

// Create three threads in the child group
Runnable runnable4 = () -> System.out.println("Thread 4 (Child) started.");
Thread thread4 = new Thread(runnable4, "Thread 4 - Child", 5);
thread4.start();

Runnable runnable5 = () -> System.out.println("Thread 5 (Child) started.");
Thread thread5 = new Thread(runnable5, "Thread 5 - Child", 5);
thread5.start();

Runnable runnable6 = () -> System.out.println("Thread 6 (Child) started.");
Thread thread6 = new Thread(runnable6, "Thread 6 - Child", 5);
thread6.start();

// Print the names and priority of all threads in both groups
printThreadInfo(parentGroup);
printThreadInfo(childGroup);

// Wait for all threads to finish
parentGroup.awaitTermination(Long.MAX_VALUE);
}

private static void printThreadInfo(ThreadGroup threadGroup) {
Enumeration<Thread> threads = threadGroup.enumerate();
System.out.println("\nThreads in " + threadGroup.getName() + ":");
while (threads.hasMoreElements()) {
Thread thread = threads.nextElement();
System.out.println(thread.getName() + " - Priority: " + thread.getPriority());
}
}
}

In this example, we create two groups: the parentGroup and a child group with the name "Child Group". We then create six threads within these groups, each with a specific priority level set using the constructor's third argument. After creating the threads, we print the names and priorities of all threads in both groups using the printThreadInfo() method. Finally, we wait for all threads to terminate using the awaitTermination() method on the parent group.

Common Mistakes

  1. Forgetting to call the start() method on a thread: This will result in a thread that never runs.
  2. Not handling exceptions properly: If an uncaught exception occurs in a thread, it can cause the entire application to crash. Make sure to use try-catch blocks or an UncaughtExceptionHandler to handle exceptions gracefully.
  3. Creating too many threads: Excessive thread creation can lead to poor performance due to context switching and increased memory usage.
  4. Not using ThreadGroups: Failing to organize threads into groups can make it difficult to monitor and control them effectively.
  5. Forgetting to call awaitTermination() or other methods to wait for threads to finish: If you don't wait for all threads to finish, your main thread may terminate before all the others, causing issues with resource cleanup or data consistency.
  6. Not setting appropriate priorities for threads within a group: If not managed properly, high-priority threads can starve low-priority threads of CPU resources.
  7. Failing to use the setDaemon() method appropriately: Setting daemon threads can cause issues if they rely on user-initiated threads for their execution or access shared resources that are not designed to be accessed concurrently.
  8. Creating cyclic relationships between groups: This is not allowed and will result in an IllegalThreadGroupException.
  9. Not setting a name for the thread group: While not essential, giving each thread group a descriptive name can make it easier to identify and manage them during debugging or monitoring.

Practice Questions

  1. Write a program that creates two groups and five threads in each group. Each thread should print its name, group name, and priority level.
  2. Modify the previous example to add an UncaughtExceptionHandler for both the parent and child groups. The handler should log any uncaught exceptions to the console.
  3. Write a program that demonstrates the destroy() method on a group, ensuring that all threads in the group are terminated properly.
  4. Create a program that uses the activeCount() and enumeration() methods to count and print the number of active threads in a specified group and its subgroups.
  5. Write a program that creates a thread pool using ThreadGroup, where each thread in the pool runs a specific task repeatedly until shut down. The program should accept command-line arguments for the number of threads, the task to be executed, and the maximum number of iterations per thread.
  6. Implement a simple producer-consumer problem using ThreadGroup and BlockingQueue. Create two groups: one for producers and another for consumers. Producers should generate data and add it to the queue, while consumers should remove data from the queue and process it. Ensure that the program runs smoothly with multiple producer and consumer threads.

FAQ

Q: What happens if I create a thread without specifying a ThreadGroup?

A: If you don't specify a ThreadGroup, the new thread will belong to the main group automatically.

Q: Can a thread be moved from one group to another?

A: No, once a thread is created, it cannot be moved to another group. However, you can create a new thread in a different group if needed.

Q: What is the purpose of the ThreadGroup class in Java?

A: The ThreadGroup class provides a way to organize threads into a hierarchical structure, making it easier to monitor and control them in multi-threaded applications. It also enables better exception handling and resource management.

Q: How can I find out which group a thread belongs to?

A: You can use the getThreadGroup() method on a Thread object to get its associated ThreadGroup.

Q: Can I create a cycle in the ThreadGroup hierarchy (e.g., Group A has Group B, and Group B has Group A)?

A: No, it is not possible to create a cyclic relationship between groups in Java. If you try to do so, an IllegalThreadGroupException will be thrown.

Q: How can I set the priority of all threads within a group programmatically?

A: You can iterate through the threads in a group using the enumeration() method and call the setPriority() method on each thread to set its priority level.

Q: What is the difference between daemon and user-initiated threads?

A: Daemon threads are low-priority background threads that run only as long as there are user-initiated threads still running. User-initiated threads are high-priority threads that keep the application alive and perform essential tasks. Setting a thread as a daemon indicates that it can be terminated when there are no more user-initiated threads left running.

Q: Can I create a group without any threads?

A: Yes, you can create an empty group by providing null as the parent group or not specifying it at all. However, since the main thread always belongs to the main group, creating an empty root group is not possible.

Java - Thread Group | Java | XQA Learn