Cooperate with thread pool to define inheritable thread variable InheritableThreadLocal

        When it comes to inheritable thread variables, you may think of the implementation java.lang.InheritableThreadLocal in jdk. It has the same function as the thread variable ThreadLocal, and when a new thread instance Thread is created on the current thread, these thread variables are passed from the current thread to the new thread instance. (At this time, thread variables are no longer thread safe, and thread safety issues need to be considered)

InheritableThreadLocal:

 

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     * @param map the map to store.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

 This class inherits ThreadLocal and overrides methods related to ThreadLocalMap. This ThreadLocalMap is actually two properties of the java thread object Thread class

 

class Thread implements Runnable {
    .......

    /* 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;
    ........
}

 These two ThreadLocalMaps are actually where the thread variables are actually stored. We use the thread variable set content, which is actually to put the content in the ThreadLocalMap. The key is the ThreadLocal itself you defined, and the value is the content you set to the thread variable. Because the content exists on the thread itself, the same thread can access as many methods as possible, and different threads cannot access or access different objects, realizing thread safety.

   ThreadLocal's set method:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

 in

ThreadLocalMap map = getMap(t);

 This line of code, ThreadLocal's getMap:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

 And the getMap of InheritableThreadLocal:

/**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    That is, InheritableThreadLocal rewrites the methods related to ThreadLocalMap. In fact, it puts the content of the set on the inheritableThreadLocals property of the thread object, while the ordinary ThreadLocal puts the content of the set on the threadLocals property of the thread object. We usually create a new thread, new Thread(), and call the init() method when the Thread class is instantiated:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess ();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        this.name = name.toCharArray();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        time = nextThreadID ();
    }

 in

if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

 This line of code passes the inheritableThreadLocals property of the current thread object to the newly created thread object inheritableThreadLocals property, that is, the transmission of thread variables is realized.

    The above is the implementation principle of inheritable thread variable inheritableThreadLocals. However, in practical application scenarios, most of them use thread pools for multi-threaded programming, so the inheritableThreadLocals class provided by jdk is not very practical. Running a Runable instance in the thread pool (ThreadPoolExecutor) does not create a new thread, but adds the Runable instance to the queue (when the number of core threads has been instantiated full), and lets the workers of the ThreadPoolExecutor take it from the queue Out the Runable instance (this is a typical producer-consumer pattern), and then run the Runable instance.run() method. Therefore, the implementation of JDK's inheritableThreadLocals cannot be applied.

     So I thought about writing an inheritable thread variable that can be passed on the executor. To achieve this function, a single-threaded variable itself is not enough, and the cooperation of the thread pool is also required. Through the ideas given by my previous blog http://xiangshouxiyang.iteye.com/blog/2354074 "Thread Pool Enhancement Implementation", I designed a custom InheritableThreadLocal:

package com.hcd;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Thread variables that can be inherited in a specific thread pool (used with InheritableThreadLocalExecutor)
 * Created by cd_huang on 2017/8/28.
 */
public class InheritableThreadLocal<T> extends ThreadLocal<T>{
	private static List<InheritableThreadLocal> inheritableExecutorThreadLocalList =new CopyOnWriteArrayList<>();

	public InheritableThreadLocal(){
        this(true);
	}

	public InheritableThreadLocal(boolean isAdd){
		/**
		 * General thread variables themselves do not need to be garbage collected
		 */
		if(isAdd){
			inheritableExecutorThreadLocalList.add(this);
		}
	}

	/**
	 * Take out the content set thread variable from the map (protected method, can be overridden, but direct calling is not recommended)
	 * @param map
	 */
	protected void setThreadLocalFromMap(Map map){
		T obj = (T)map.get(this);
		this.set(obj);
	}

	/**
	 * Get thread variables are loaded into map (protected method, can be overridden, but direct calling is not recommended)
	 * @param map
	 */
	protected void getThreadLocalputMap(Map map){
		T obj = this.get();
		map.put(this,obj);
	}

	/**
	 * Remove the thread variable (protected method, can be overridden, but direct call is not recommended)
	 */
	protected void removeThreadLocal(){
        this.remove();
	}

	/**
	 * Put the content of the thread variable that can be passed by the current thread in the map, and call it before the task is put into the thread pool queue
	 * @return
	 */
	public static Map<Object,Object> getThreadLocalsMap(){
		Map<Object,Object> threadLocalMap =new HashMap<>();
		List<InheritableThreadLocal> list =inheritableExecutorThreadLocalList;
		for(InheritableThreadLocal threadLocal:list){
			threadLocal.getThreadLocalputMap(threadLocalMap);
		}
		return threadLocalMap;
	}

	/**
	 * Reset the content in the map to the content of the thread variable and call it before the task actually runs the run method
	 * @param threadLocalMap
	 */
	public static void setThreadLocalsFromMap(Map<Object,Object> threadLocalMap){
		List<InheritableThreadLocal> list =inheritableExecutorThreadLocalList;
		for(InheritableThreadLocal threadLocal:list){
			threadLocal.setThreadLocalFromMap(threadLocalMap);
		}
	}

	/**
	 * Clear the thread variable content of the setThreadLocalsFromMap method set, and call it after the task actually runs the run method
	 */
	public static void removeThreadLocals(){
		List<InheritableThreadLocal> list =inheritableExecutorThreadLocalList;
		for(InheritableThreadLocal threadLocal:list){
			threadLocal.removeThreadLocal();
		}
	}
}

 InheritableThreadLocalExecutor that works with it:

package com.hcd;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.*;

/**
 * Thread pool that supports inheritable thread variables (used with InheritableThreadLocal)
 * Created by cd_huang on 2017/8/29.
 */
public class InheritableThreadLocalExecutor extends ThreadPoolExecutor {

	private static Logger logger = LoggerFactory.getLogger(InheritableThreadLocalExecutor.class);

	public InheritableThreadLocalExecutor(int corePoolSize,
	                                      int maximumPoolSize,
	                                      long keepAliveTime,
	                                      TimeUnit unit,
	                                      BlockingQueue<Runnable> workQueue) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
	}

	public InheritableThreadLocalExecutor(int corePoolSize,
	                                      int maximumPoolSize,
	                                      long keepAliveTime,
	                                      TimeUnit unit,
	                                      BlockingQueue<Runnable> workQueue,
	                                      ThreadFactory threadFactory) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
	}
	public InheritableThreadLocalExecutor(int corePoolSize,
	                                     int maximumPoolSize,
	                                     long keepAliveTime,
	                                     TimeUnit unit,
	                                     BlockingQueue<Runnable> workQueue,
	                                     RejectedExecutionHandler handler) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
	}

	public InheritableThreadLocalExecutor(int corePoolSize,
	                                     int maximumPoolSize,
	                                     long keepAliveTime,
	                                     TimeUnit unit,
	                                     BlockingQueue<Runnable> workQueue,
	                                     ThreadFactory threadFactory,
	                                     RejectedExecutionHandler handler) {
		super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
	}

	/**
	 * Override the method of executing the thread instance
	 * @param command
	 */
	@Override
	public void execute(Runnable command) {
		if (command == null){
			throw new NullPointerException();
		}
		TaskWithThreadLocal task =new TaskWithThreadLocal(command,InheritableThreadLocal.getThreadLocalsMap());
		super.execute(task);
	}

	/**
	 * The new thread is executed with the specified thread information
	 * @param
	 */
	private static class TaskWithThreadLocal implements Runnable{
		private Map<Object,Object> threadLocalMap;
		private Runnable delegate;

		public TaskWithThreadLocal(Runnable delegate, Map<Object,Object> threadLocalMap){
			this.delegate =delegate;
			this.threadLocalMap =threadLocalMap;
		}

		/**
		 * Rewrite the run method, set the thread variable before executing the run method, and clear the thread variable after executing the run method
		 * At the same time, the exception information at runtime is printed, and the exception at the runtime of delegate.run() is swallowed, not thrown out
		 * (The thread pool will throw an exception after the task runs abnormally by default, and destroy the thread object itself, that is, if each task runs abnormally, then the efficiency of using the thread pool is not as good as creating a new thread directly. For details, see ThreadPoolExecutor class 1123 line runWorkers method)
		 * The meaning of this processing by the jdk thread pool should be to hope that by throwing the exception, the exception will be captured and processed by the exception handling interceptor that comes with the thread object itself or the JVM's default global exception handling interceptor.
		 * Here directly call the interceptor processing, do not throw exceptions, avoid the destruction of thread instances
		 */
		@Override
		public void run() {
			InheritableThreadLocal.setThreadLocalsFromMap(threadLocalMap);
			try{
				try{
					delegate.run();
					//Because the exception in the execution process of the callable's call() method will be handled by the run() method of its calling superior FutureTask, the exception will not be thrown out. In order to print the exception log, the processing of the exception log printing is carried out uniformly.
					if(delegate instanceof FutureTask){
						try{
							((FutureTask)delegate).get();
						}catch (Throwable e){
							logger.error(e.getMessage(),e);
							Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),e);
						}
					}
				}catch (Throwable e){
					logger.error(e.getMessage(),e);
					Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(),e);
				}
			}finally {
				InheritableThreadLocal.removeThreadLocals();
			}
		}
	}
}

 When the Runable instance is put into the thread pool, a new TaskWithThreadLocal class is created, and the thread variable is placed in the threadLocalMap. What is actually placed in the thread pool queue is TaskWithThreadLocal. When the run method of the TaskWithThreadLocal class is executed, the run method of the Runable instance that you really want to execute will be executed. Use the TaskWithThreadLocal class to pass threadLocalMap content, which is somewhat similar to jdk's InheritableThreadLocal class passing ThreadLocal.ThreadLocalMap inheritableThreadLocals when Thread is instantiated.

     For example, we use thread variable to record userId. UserIdUtil:

public class UserIdUtil {
	private static final InheritableThreadLocal<String> userIdLocal = new InheritableThreadLocal<>();

	public static String getUserId(){
		return userIdLocal.get();
	}

	public static void setUserId(String userId){
		userIdLocal.set(userId);
	}

	public static void removeUserId(){
		userIdLocal.remove();
	}
}

You only need to change the original ThreadLocal class to InheritableThreadLocal class to pass thread variables in the InheritableThreadLocalExecutor thread pool.

 

 

 

 

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326918153&siteId=291194637