preface

When making a distributed link tracking system, it is necessary to solve the requirement of asynchronous call transparent transmission context, especially the transmission of traceId. This paper analyzes several methods of thread pool transparent transmission.

Other typical scenario examples:

  1. Distributed tracking system or full link pressure measurement (link marking)

  2. Log collection Records the system context

  3. The Session level Cache

  4. The application container or upper framework passes information across the application code to the lower SDK

JDK support for cross-threading ThreadLocal

Let’s start with the simplest scenario, which is also an example of a mistake.

    void testThreadLocal(a){
        ThreadLocal<Object> threadLocal = new ThreadLocal<>();
        threadLocal.set("not ok");
        new Thread(()->{
            System.out.println(threadLocal.get());
        }).start();
    }
Copy the code

In Java, threadLocal is tied to a thread. The value of a set you set in one thread is not available in another thread.

The output above is:

null

1.1 InheritableThreadLocal example

The JDK takes this scenario into account and implements InheritableThreadLocal. Don’t get too excited, this only supports parent-child threads, and thread pools will be problematic.

Let’s look at the InheritableThreadLocal example:

        InheritableThreadLocal<String> itl = new InheritableThreadLocal<>();
        itl.set("father");
        new Thread(()->{
            System.out.println("subThread:" + itl.get());
            itl.set("son");
            System.out.println(itl.get());
        }).start();

        Thread.sleep(500);// Wait for the child thread to finish executing

        System.out.println("thread:" + itl.get());
Copy the code

The output above is:

SubThread :father // The child thread can get the parent thread’s variables

son

Thread :father // Child thread changes do not affect the parent thread variables

1.2 Implementation principle of InheritableThreadLocal

If you’re wondering how InheritableThreadLocal works, it’s pretty simple. Is recorded the ThreadLocal Thread class inside part, InheritableThreadLocal ThreadLocalMap, initialization, can get the parent InheritableThreadLocal. Directly on the code can be seen very clearly.

class Thread {.../* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; .if(inheritThreadLocals && parent.inheritableThreadLocals ! =null)
    this.inheritableThreadLocals =
    ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
Copy the code

The InheritableThreadLocal class in the JDK completes value passing from parent thread to child thread. But in the case of execution components that are pooled and reused by threads, such as thread pools, threads are created by thread pools and are pooled and reused; In this case, parent-child ThreadLocal value passing is meaningless, and the application actually needs to pass the ThreadLocal value when the task is submitted to the thread pool to the task execution time.

2. Logging MDC/Opentracing implementation

If your application implements the Opentracing specification, such as intercepting thread pools via SkyWalking’s Agent, then the custom Scope implementation class can pass MDC across threads, and your obligations can then be passed to child threads by setting the value of the MDC.

The code is as follows:

        this.scopeManager = scopeManager;
        this.wrapped = wrapped;
        this.finishOnClose = finishOnClose;
        this.toRestore = (OwlThreadLocalScope)scopeManager.tlsScope.get();
        scopeManager.tlsScope.set(this);
        if (wrapped instanceof JaegerSpan) {
            this.insertMDC(((JaegerSpan)wrapped).context());
        } else if (wrapped instanceof JaegerSpanWrapper) {
            this.insertMDC(((JaegerSpanWrapper)wrapped).getDelegated().context());
        }
Copy the code

3, Ali transmittable-thread-local

Github address: github.com/alibaba/tra…

TransmittableThreadLocal (TTL) is a Java™ STD lib (simple and 0 dependent) missing from framework/middleware that provides enhanced InheritableThreadLocal to transfer values between threads even using thread pool components.

If there is any issues with transmittable-thread-local

The class TransmittableThreadLocal is used to hold the values and pass them across the thread pool.

TransmittableThreadLocal inherits InheritableThreadLocal in a similar manner. Instead of InheritableThreadLocal, add

  1. The copy method is used to customize the copy behavior when a ThreadLocal value is passed from the task submitted to the thread pool to the task executed. By default, a reference is passed. Note: if the passed object references across threads because there is no thread closed, with InheritableThreadLocal. ChildValue, user/business logic should pay attention to transfer the object’s thread

  2. Protected beforeExecute/afterExecute method to perform a task (Runnable/Callable) before/after the life cycle of the callback, the default is empty.

3.2 The code with transmittable and thread-local is provided

Method 1: TtlRunnable encapsulation:

ExecutorService executorService = Executors.newCachedThreadPool();
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Set in the parent thread
context.set("value-set-in-parent");

// For additional processing, generate the modified object ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(() -> {
    System.out.println(context.get());
});
executorService.submit(ttlRunnable);
Copy the code

Option 2: ExecutorService Package:

ExecutorService executorService = ...
// Additional processing, generating modified object executorService
executorService = TtlExecutors.getTtlExecutorService(executorService);
Copy the code

Method 3: Use Java Agent without code intrusion

This way, the thread pool implementation is transparent, and there is no code in the business code to modify Runnable or thread pool. That is, the application code can be non-intrusive.

ExecutorService executorService = Executors.newCachedThreadPool();
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
/ / = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Set in the parent thread
context.set("value-set-in-parent");

executorService.submit(() -> {
    System.out.println(context.get());
});
Copy the code

4. Implementation of GRPC

GRPC is a distributed invocation protocol and implementation that encapsulates a set of cross-thread delivery context implementations.

Io.grpc. Context indicates the Context, which is used to transfer user login information and tracing information in a GRPC request link.

Context is commonly used as follows. First get the current context, which is usually passed in as an argument, or get the current existing context through current().

It then binds to the current thread using the attach method and returns the current thread

    public Runnable wrap(final Runnable r) {
        return new Runnable() {
            @Override
            public void run(a) {
                Context previous = attach();
                try {
                    r.run();
                } finally{ detach(previous); }}}; }Copy the code

The main method of Context is as follows

  • By attaching () attach the Context itself to a new scope, the new scope takes this Context instance as current and returns the previous current Context
  • Detach (Context toDetach) the reverse of attach(), which exits the current Context and detach to toDetachContext, one detach for each attach method, This is typically done through a try Finally code block or wrap template method.
  • Static storage() gets storage, which is used to attach and detach the current context.

Thread pool passing implementation:

ExecutorService executorService = Executors.newCachedThreadPool();
Context.withValue("key"."value");

execute(Context.current().wrap(() -> {
            System.out.println(Context.current().getValue("key"));
        }));
Copy the code

5, summary

The simplest of the four ways to implement cross-thread passing described above is to define a Runnable and add attribute passing. If there is any generic type, the middleware needs to wrap an Executor object like the transmittable-thread-local implementation, or use the transmittable-thread-local implementation directly.

In practical projects, to support span, MDC, RPC context, business custom context, you can refer to the above method encapsulation.

The resources

1 – the context] [GRPC source code analysis www.codercto.com/a/66559.htm…

Threadlocal variables pass through. Have you encountered any of these problems? Cloud.tencent.com/developer/a…

Scan the QR code to follow the public account “Ape Must Pass”

Answer the “interview questions” and collect them yourself.

Wechat group for discussion, please add wechat account: zyhui98, note: interview questions add group

This article is published by YBG. Unauthorized reprint is prohibited. Violators are subject to legal liability