How to create links with ThreadGroup recording threads

background

We maintain a monitoring platform, which runs a variety of plug-ins to test different scenarios. The platform captures the standard output of the plug-in to get the log, and obtains the exit code to know the operation result.
A Java plug-in is an executable. jar file that will be loaded into the JVM of the platform and run in a thread pool. For Java plug-ins, we provide an SDK to customers to reduce development difficulty, mainly including some tools and methods, defining standard execution process, exception capture and other functions.

difficulty

In the Java SDK, we want to provide a tool and method to uniformly print standard output. In the Java plug-in, you can easily output information by calling a method similar to PluginLogger.print(). At the same time, we also need to support the printing of some logs with platform identifiable format. These logs can be temporarily saved by calling PluginLogger.append(), and then finally called PluginLogger.flush() to print uniformly. The pseudo code is as follows:

PluginLogger.append("step 1");
PluginLogger.append("step 2");
...
PluginLogger.append("step n");
PluginLogger.flush();

The final output is similar:

["step 1", "step 2"... "step n"]

Preliminary assumption

Because there will be multiple Plugin threads running on the platform at the same time, to ensure the thread safety of the temporarily stored log information, it can be thought of immediately that each Plugin thread maintains a ThreadLocal variable to save the log data of the current thread, so as to isolate the logs among threads without affecting each other:

public class PluginLogger {
    ThreadLocal<LogHolder> logger = new ThreadLocal<>();

    public static void append(message) {
        logger.get().append(message);
    }
    public static void flush() {
        logger.get().flush();
    }
}

run into a stone wall

This design is not a big problem for some single threaded plug-ins. However, when there are multiple threads in the plug-in, we find that the log s printed in the sub threads (threads created by the Plugin thread) are lost. Because the child thread and the Plugin thread have their own separate ThreadLocal variable.
To solve this problem, it is necessary to share the log context between the Plugin sub thread and the Plugin thread, and keep it independent from other threads. According to this requirement, under the previous design, when Plugin creates a sub thread, it needs to pass down the corresponding log instances together. However, this will bring great inconvenience to use.

New design

Is there any way to know the parent thread of the current thread? If you know, if the thread itself is a plugin thread, you can directly call PluginLogger.append(); If it is not a Plugin thread, then the parent thread of the search thread is always found to be Plugin thread, and then the append () of the corresponding log instance is called.

ThreadGroup

The official description of thread group is:

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.

A thread group represents a group of threads. Except for the initial thread group, all threads have a father.
Thread group has existed since Java 1.0. It was originally designed to manage threads. However, thread groups have been completely compared since Java 1.5 provided thread pools, so they are rarely useful now. However, in our project scenario, the thread group can meet our needs.

Hierarchy of thread groups

Each thread belongs to a thread group. The init() method of the thread will be called during initialization:

public class Thread implements Runnable {
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        // Omitting judgment logic
        ...

        Thread parent = currentThread();
        if (g == null) {
            g = parent.getThreadGroup();
        }
        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();
        g.addUnstarted();

        this.group = g;
        // Omit other logic
        ...
    }
}

You can see that if the ThreadGroup parameter is passed in the thread construction method, the thread belongs to this ThreadGroup; If there is no transmission, this thread shares a ThreadGroup with the parent thread (the thread that created this thread). How does ThreadGroup maintain the hierarchical relationship created? Look at the source code:

public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    /**
     * Creates an empty Thread group that is not in any Thread group.
     * This method is used to create the system Thread group.
     */
    private ThreadGroup() {     // called from C code
        this.name = "system";
        this.maxPriority = Thread.MAX_PRIORITY;
        this.parent = null;
    }
    public ThreadGroup(String name) {
        this(Thread.currentThread().getThreadGroup(), name);
    }
    /**
     * @exception  NullPointerException  if the thread group argument is <code>null</code>.
     */
    public ThreadGroup(ThreadGroup parent, String name) {
        this(checkParentAccess(parent), parent, name);
    }

    private ThreadGroup(Void unused, ThreadGroup parent, String name) {
        this.name = name;
        this.maxPriority = parent.maxPriority;
        this.daemon = parent.daemon;
        this.vmAllowSuspension = parent.vmAllowSuspension;
        this.parent = parent;
        parent.add(this);
    }
}

ThreadGroup provides public and private construction methods. It can be found from the comments that the first private method is called from the C code. It creates a system Thread group corresponding to the main thread. The public method needs to provide a ThreadGroup parameter as the parent object of the current ThreadGroup. It can be seen that the top level of ThreadGroup is system Thread group, and its parent is null; For other threadgroups, its parent defaults to the ThreadGroup that created the thread.

Solving problems with ThreadGroup

Back to the difficulties we face, we need to know the creation link of threads, and the requirements can be met through ThreadGroup. Therefore, the new design is roughly as follows:

  1. Assign a separate ThreadGroup to each Plugin thread.
  2. Create a ConcurrentHashMap and make the ThreadGroup of Plugin correspond to the log instance one by one.
  3. In the PluginLogger.append() method, judge whether the ThreadGroup of the current thread is in the above Map. If it is, skip to step 5 directly, otherwise go to step 4.
  4. Go up through ThreadGroup.getParent() until you find the ThreadGroup in the Map.
  5. Get the corresponding log instance from the Map and print the log.

Under such design, for Plugin developers, the method of calling PluginLogger from anywhere in any thread gets the same log context.

summary

  1. Although ThreadGroup is a semi obsolete class in Java, it is still very useful for tracking thread creation links.
  2. For SDK developers, they should provide simple and easy-to-use interfaces or methods, keep it stupid simple.
  3. There are more methods than problems.

Keywords: Thinking

Added by s_r_elliott on Mon, 22 Nov 2021 00:58:06 +0200