Thread grouping in Java

Thread grouping in Java

Very often, threads are an imaginary thing for programmers that you don’t always need to remember. Today I will go a little further and tell you about another new instance for many of you – ThreadGroup. If you are not too comfortable with the topic of threads, I highly recommend that you first understand the very basics of the idea of threads and how they work.

So in short, based on the information contained on the official Oracle website (link):

A thread group represents a set of threads. In addition, a thread group can also include other thread groups. The thread groups form a tree in which every thread group except the initial thread group has a parent.

A thread is allowed to access information about its own thread group, but not to access information about its thread group’s parent thread group or any other thread groups.

Now it’s my turn – I’ll try to explain it to you in a simple way.

Each Java thread is a member of the thread group. Groups allow you to perform operations on many threads simultaneously (e.g. you can start or suspend all threads belonging to the same group with a single call of the appropriate method). The ThreadGroup class in the java.lang package is used to group threads.

While the program is running, threads are placed in groups as they are created. It is up to the programmer to decide which group to assign the thread to. He may decide that the thread should go to the default group or to a group defined and created in the program. Once a thread is assigned to a group, it cannot be reassigned later.

ThreadGroup can contain not only threads themselves, but other thread groups as well. The highest in the hierarchy of groups in Java applications is a group called main. You can create threads and thread groups in the subgroups of the main group by creating a tree structure.

The Default Thread Group

If the constructor call when creating a new thread did not contain a group specification, the new thread will be assigned to the same group as the group of the thread in which it was created. The primary thread group is a group called main. This group is created by the system when the Java application is run for the first time. If the developer does not modify the way new objects are created, all threads will be placed in the same group.

Create a thread group

ThreadGroup objects are created by constructors. This class has two main constructors. If in the parameter list we give only the value of the name of this object, then the parent of this new group will be the thread group of the currently running thread. In the other hand, the parent of this new group is the specified thread group as constructor parameter.

public ThreadGroup(String name)
public ThreadGroup(ThreadGroup parent, String name)

Threads are assigned to groups when they are created. There are three constructors in the Thread class to do this:

public Thread (ThreadGroup group, Runnable runnable)
public Thread (ThreadGroup group, String name)
public Thread (ThreadGroup group, Runnable runnable, String name)

Each of these three constructors above creates a new thread, initializes it according to the values of the Runnable and String parameters, and assigns the thread to the given group. In the example below, a new thread group is declared and created, to which the newly created thread is assigned:

ThreadGroup ourThreadGroup = new ThreadGroup("Our group of Threads");
Thread ourThread = new Thread(ourThreadGroup, "Thread for our group");

The ThreadGroup parameter passed to the thread constructor need not be a user-defined group. It can be a group created by the Java runtime system, or a group created by the application in which the applet was run (e.g. by a browser). Sometimes new threads appear during our work that we would like to know a little more about. To find out which group a thread belongs to, just call the getThreadGroup() method;

ThreadGroup threadGroup = new ThreadGroup("Simple group of threads");
Thread oneThread = new Thread(threadGroup, "One thread");
ThreadGroup group = oneThread.getThreadGroup();
System.out.print(group);    // java.lang.ThreadGroup[name=Simple group of threads,maxpri=5]

ThreadGroup methods

The thread group classes and their associated libraries contain a large set of different methods and functions. In this article, I’ll show you some useful features that you should know about.

Collection Management Methods

They are used to manage the thread collections and the subgroups that the group contains.

The activeCount() method returns the current number of active threads in the group plus any group for which this thread is parent. Often used in conjunction with the enumerate method to obtain an array of references to all active threads in a group.

Example: (creating an array of active threads and printing their names)

public class CounterApp {
    public static void main(String[] args) {
        ThreadGroup groupNorm = new ThreadGroup("A group with normal priority");
        Thread threadOne = new SpeedThread(groupNorm, "A thread number 1");
        Thread threadTwo = new SpeedThread(groupNorm, "A thread number 2");

        System.out.println("ACTIVE: " + groupNorm.activeCount());    // ACTIVE: 2
    }
}

Methods operating on the group

They are used to write or read group (ThreadGroup object) attributes.

The ThreadGroup class provides many set and read attributes that apply to the group as a whole.
These attributes include following parameters:
– the maximum priority that a thread belonging to the group can have,
– the parameter indicating whether the group is a daemon („daemon” group),
– group name,
– group father.

The methods that set and read parameters of the ThreadGroup class operate at the group level. Thanks to them, you can read or change an attribute of the ThreadGroup class object without affecting any of the threads within the group. The group-level methods are as follows:
– getMaxPriority and setMaxPriority
– getDaemon and setDaemon
– getName
– getParent and parentOf
– toString

For example, if setMaxPriority changes the maximum priority of threads in a group, the attribute of only the object representing the group is changed, but the priorities of the threads themselves within the group remain unchanged.

Example: (creating a thread group and changing the priority value)

public static void main(String[] args) {
    ThreadGroup groupNorm = new ThreadGroup("A group with normal priority");
    Thread threadMax = new Thread(groupNorm, "A thread with maximum priority");

     // set Thread's priority to max (10)
    threadMax.setPriority(Thread.MAX_PRIORITY);

    // set ThreadGroup's max priority to normal (5)
    groupNorm.setMaxPriority(Thread.NORM_PRIORITY);

   System.out.printf("Group's maximum priority: %s\n", groupNorm.getMaxPriority());
   System.out.printf("Thread's priority: %s\n", threadMax.getPriority());
}

When a ThreadGroup groupNorm object is created, it inherits the maximum priority of the thread group it derives from (the father group). In this case it is the maximum (MAX_PRIORITY) set by the Java runtime system. Then a threadMax thread is created and assigned the maximum priority allowed by the Java runtime system. Next, the priority for the thread group is lowered to (NORM_PRIORITY). Calling the setMaxPriority method here does not affect the threadMax thread priority. So we have a situation where the threadMax thread has a priority of 10, which is higher than the maximum priority of the groupNorm group it belongs to.

The output of the program is:

Group's maximum priority: 5
Thread's priority: 10 

So the setMaxPriority method only affects threads in the group that were created before it was called. Instead, it affects all new threads created in the group, all subgroups, and threads for which the setPriority method was called. Similarly, the daemon status change will apply only to new threads in the group and subsequent subgroups. The „daemon” status of a thread group means that the group will be destroyed if all threads in the group have terminated.

Methods operating on all threads within the group:

For this group of functions, I have assigned three methods that can change the state of all threads in the group and in subgroups at the same time:
-resume
-stop
-suspend

Methods of limiting access:

The ThreadGroup class by itself has no way of restricting the access rights of threads in one group to methods of threads in another group. To introduce such restrictions, the Thread and ThreadGroup classes cooperate with security managers (SecurityManager).

Both classes (Thread class and ThreadGroup class) have the checkAccess method. This method calls the checkAccess function of the current security manager. If access is not allowed, the checkAccess method throws a SecurityException. Otherwise, checkAccess simply exits.

Listed below is the list of ThreadGroup class methods that call the checkAccess method of the ThreadGroup class before their execution (these are regulated access methods):
-ThreadGroup(ThreadGroup parent, String name)
-setDaemon(boolean isDaemon)
-setMaxPriority(int maxPriority)
-stop
-suspend
-resume
-destroy

And below is the list of methods that call the checkAccess method before their execution but for Thread class:
-all constructors for Thread class in which the Thread Group is defined
-stop
-suspend
-resume
-setPriority(int priority)
-setName(String name)
-setDaemon(boolean isDaemon)

A standalone Java application does not have a security manager by default. So there are no restrictions on accessing and modifying parameters for threads in a group. Changes can be made by one thread to another, no matter what group they come from. To implement your own access security manager, you just need to create a class derived from the SecurityManager class, override the appropriate methods in this class and then install SecurityManager as the current security manager for the application being launched.


Write a comment