The background,

ThreadLocal solves the “thread-safety problem.”

It can also be used as context staging data for later retrieval.

However, ThreadLocal can indeed fail badly, and some teams do not allow ThreadLocal.

One of the core reasons is that it’s easy to forget to clean up, and reuse in a thread pool environment leads to a string environment.

So, is there an elegant solution? This paper presents a solution of our own.

Second, the solution

package basic.thread;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.HashMap;
import java.util.Map;

public class ThreadContext {

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new TransmittableThreadLocal<>();

    /** * Initialize the context */
    public static void initContext(a) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {

            CONTEXT.set(new HashMap<>(8));
        } else{ CONTEXT.get().clear(); }}/** * clear context */
    public static void clearContext(a) {
        CONTEXT.remove();
    }

    /** * get the context */
    public static <T> T getValue(String key) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            return null;
        }
        return (T) con.get(key);
    }

    /** * Sets the context parameter */
    public static void putValue(String key, Object value) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            CONTEXT.set(new HashMap<>(8)); con = CONTEXT.get(); } con.put(key, value); }}Copy the code

2.1 Recommendations in the Java Development Manual

Write as follows:

    public Result<R> executeAbility(T ability) {
           // Initialize the context
            ThreadContext.initContext();
        try {
            // Omit the core business code

        } finally{ ThreadContext.clearContext(); }}Copy the code

2.2 Further improvement

Most people would stop there, but I don’t think it’s enough. How do you avoid forgetting to clean up threadLocal?

Are there any similar examples in the JDK source code? The JDK provides try-with-resource to make it easier to release resources. Users do not need to manually write finnalys to release resources.

Common cases:Use the try – with – the resource

We also know that you can customize try-with-resource resources by implementing AutoCloseable.

It turned out not to be a good fit, because ThreadLocal utility classes are usually static in a pass-context scenario, and even if static doesn’t work, it’s not convenient to pass the object when you get a property.

Of course, if you don’t want to use it statically, you can also consider implementing the AutoClosebale interface using the try-with-resource mechanism.

Could we adopt a similar mechanism?

You can directly privatize the initialization and cleanup methods, provide no-argument and return value encapsulation, pass in calls as arguments using Runnbale and Callable, and encapsulate try-finally logic in encapsulated methods.

package basic.thread;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;

public class ThreadContext {

    private static final ThreadLocal<Map<String, Object>> CONTEXT = new TransmittableThreadLocal<>();

    /** * Initialize the context */
    private static void initContext(a) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {

            CONTEXT.set(new HashMap<>(8));
        } else{ CONTEXT.get().clear(); }}/** * clear context */
    private static void clearContext(a) {
        CONTEXT.remove();
    }

    /** * get the context */
    public static <T> T getValue(String key) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            return null;
        }
        return (T) con.get(key);
    }

    /** * Sets the context parameter */
    public static void putValue(String key, Object value) {
        Map<String, Object> con = CONTEXT.get();
        if (con == null) {
            CONTEXT.set(new HashMap<>(8));
            con = CONTEXT.get();
        }
        con.put(key, value);
    }

    /** * Automatic collection of packages */
    public static void  runWithAutoClear(Runnable runnable){
        initContext();
        try{
            runnable.run();
        }finally{ CONTEXT.remove(); }}/** * Automatic collection of packages */
    public static <T> T callWithAutoClear(Callable<T> callable){
        initContext();
        try{
            try {
                return callable.call();
            } catch (Exception e) {
                throw newRuntimeException(e); }}finally{ CONTEXT.remove(); }}}Copy the code

Usage reference:

   public Result<R> executeAbility(T ability) {
      return  AbilityContext.callWithAutoClear(()->{
        		 // Business core code
       });
    }
Copy the code

Third, some questions

3.1 Normally thread contexts are used across classes, so is this pointless?

Usually the thread context tool class is wrapped in the outermost layer of the tool that needs to use the context. It can also be applied directly to RPC’s interface implementation layer or Controller methods.

If the entire call involves multiple classes, the put or get methods of ThreadContext can still be used in subfunctions or thread calls as long as they are initiated in the same thread (with TransmittableThreadLocal).

Four,

As long as the mind does not slide, the method is more than difficult.

We should find a way to solve the problem, not you evading it. When you see that some solutions are still error-prone, look for ways to make further improvements.

Of course, if you don’t want to use ThreadLocal and still want to temporarily store objects for subsequent sessions, you can define context objects that are passed between execution steps.

Similar articles include: triggering specific behavior implementations when a Map changes


Creation is not easy, if this article is helpful to you, welcome to like, collect and pay attention to, your support and encouragement, is the biggest power of my creation.