初窥ThreadLocal工作原理

1. ThreadLocal是什么?有什么作用?

ThreadLocal一般认为线程本地变量,其主要为线程提供一份共享变量的本地副本,因此,每个线程可以单独访问本地副本,而不受其他线程的影响。

2. ThreadLocal是如何保证线程安全的?

ThreadLocal主要从两方面保证了线程安全,其中一方面,通过为每个线程提供独立的ThreadLocalMap<key, value>实例对象,该实例对象以threadLocal实例作为键值,共享变量的副本作为value值,因此,不同线程持有的ThreadLocalMap实例对象时不同的;另一方面,ThreadLocalMap可以同时保存不同ThreadLocal实例对象保存的本地副本,因此同一个线程可以存储不同ThreadLocal实例存储的本地副本。

3. ThreadLocal的工作原理?

在了解ThreadLocal工作原理之前,首先了解下ThreaLocal类提供的几个公共方法:

(1) public T get() { }
(2) public void set(T value) { }
 (3)public void remove() { }
 (4)protected T initialValue() { }

其中,get()方法用户获取当前ThreadLocal实例对象保存的本地副本;set()方法主要为当前ThreadLocal实例对象设置新的副本对象;remove()方法主要移除当前ThreadLocal实例对象保存的本地副本;initialValue()方法主要为当前ThreadLocal实例对象初始化,一般在使用需要重写,是一个延迟加载方法。

首先,看下ThreadLocal是怎么样为线程创建独立的本地副本,查看ThreadLocal类的get()方法源码得知:

 /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();

    }

在get()方法中,首先获得当前线程实例,其次,通过getMap()方法获取当前线程的ThreadLocalMap实例,由于ThreadLocalMap实例通过ThreadLocal实例作为键值存储本地副本,所以通过map.getEntry(this)获取存储本地副本的键值对。

接下来,看下getMap()方法的源码实现:

/**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;

    }

getMap()方法主要获取当前线程t持有的ThreadLocalMap对象。

接着看setInitialValue()方法的源码实现:

 /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

在setInitialValue()方法中,首先需要初始化一个value(本地副本实例对象),其次通过当前线程对象获取到ThreadLocalMap实例,若当前map对象不为空,则将value值添加至该map对象中,其中this就是当前ThreadLocal对象实例本身;若map对象为空,则创建一个ThreadLocalMap对象。

我们接着往下看createMap(t, value)做了哪些事情,

 /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);

    }

 /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

 /**
         * The initial capacity -- MUST be a power of two.
         */

        private static final int INITIAL_CAPACITY = 16;

createMap方法主要为当前线程t创建一个ThreadLocalMap实例,该Map实例用于存储当前线程的ThreadLocal对象保存的所有本地副本实例。其中ThreadLocalMap对象的初始化容量为16,类似于HashMap的初始化过程。上述为get方法所涉及的相关处理过程。

接下来看看set(T value)方法的源码实现

 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

从set(T value)源码可以看出和setInitialValue()的流程很相似,就不再重复叙述了。

接着看remove()方法的源码实现,

/**

     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

从remove的源码实现中,可以看出该方法主要用于移除以当前ThreadLocal实例生成的Entry键值对。

在我们看完ThreadLocal类中常用方法的源码实现后,是否仍存在一个疑问?ThreadLocal类是如何为共享变量创建一个当前线程的一个本地副本对象呢?然而在揭开这个疑问之前,我们需要了解下,ThreadLocal类提供一个供子类实现的initialValue()方法,以下是该方法的源码实现。

/**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;

    }

从源码中,可以看出ThreadLocal类并未对该方法实现做相应的工作,只是简单的返回一个null值,因此,当我们在实际使用的过程中需要我们自己去重新实现该方法,用于创建一个本地副本实例。

4. 测试验证

在完成ThreadLocal类的相关源码学习后,现进行实例测试验证, 测试代码如下:

public class Test {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
            ThreadLocal<Demo> threadLocal = new ThreadLocal<Demo>(){
                @Override
                public Demo initialValue(){
                    Demo demo = new Demo();
                    System.out.println("intial value, thread-name: "+ Thread.currentThread()+" demo: "+demo+", demoName: "+demo.getName());
                    return demo;
                }
                
            };
            
            Demo demo = new Demo();
            demo.setName("zhansan");
            demo.setRole("admin");
            for(int i=0; i<2; i++){
                Thread thread = new Thread(new WorkRunnable(threadLocal, demo, "testName-"+i, "testRole-"+i));
                thread.setName("thread-"+i);
             
                thread.start();
            }
    }
    
    private static class WorkRunnable implements Runnable{
        private ThreadLocal<Demo> threadLocal;
        private String name;
        private String role;
        
        public WorkRunnable(ThreadLocal<Demo> threadLocal,  Demo demo, String name, String role) {
            // TODO Auto-generated constructor stub
            this.threadLocal = threadLocal;
            threadLocal.set(demo);
            this.name =name;
            this.role = role;
            System.out.println("thread-name: "+Thread.currentThread()+ "; demo: "+demo);
        }
        @Override
        public void run(){
            Demo demo = threadLocal.get();
            //demo.setName(name);
            //demo.setRole(role);
            System.out.println("thread name: "+Thread.currentThread()+"; demo: "+ demo+"; demo.name: "+demo.getName()+"; demo.role: "+demo.getRole());
        }
    }

}

测试结果如下:

thread-name: Thread[main,5,main]; demo: Demo@2a139a55
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
intial value, thread-name: Thread[thread-0,5,main] demo: Demo@26830c2b, demoName: null
intial value, thread-name: Thread[thread-1,5,main] demo: Demo@15449d43, demoName: null
thread name: Thread[thread-1,5,main]; demo: Demo@15449d43; demo.name: null; demo.role: null
thread name: Thread[thread-0,5,main]; demo: Demo@26830c2b; demo.name: null; demo.role: null

从该测试结果看出,ThreadLocal类为当前线程创建的本地副本是通过initialValue()创建完成的,由于在创建本地副本对象时为写入相关属性,因此输出的属性值均为null.

现我们通过添加这段代码后,并查看测试结果:

 demo.setName(name);
 demo.setRole(role);

thread-name: Thread[main,5,main]; demo: Demo@2a139a55
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
intial value, thread-name: Thread[thread-0,5,main] demo: Demo@15449d43, demoName: null
intial value, thread-name: Thread[thread-1,5,main] demo: Demo@6304a40b, demoName: null
thread name: Thread[thread-1,5,main]; demo: Demo@6304a40b; demo.name: testName-1; demo.role: testRole-1

thread name: Thread[thread-0,5,main]; demo: Demo@15449d43; demo.name: testName-0; demo.role: testRole-0

通过测试结果,看出ThreadLocal保存的当前线程副本中属性均已被赋值。

假设我们在ThreadLocal子类中未实现initialValue()方法,那将会发生什么情况呢?相关测试代码及测试结果如下:

public class Test {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
            ThreadLocal<Demo> threadLocal = new ThreadLocal<Demo>();
//            {
//                @Override
//                public Demo initialValue(){
//                    Demo demo = new Demo();
//                    System.out.println("intial value, thread-name: "+ Thread.currentThread()+" demo: "+demo+", demoName: "+demo.getName());
//                    return demo;
//                }
//                
//            };
            
            Demo demo = new Demo();
            demo.setName("zhansan");
            demo.setRole("admin");
            for(int i=0; i<2; i++){
                Thread thread = new Thread(new WorkRunnable(threadLocal, demo, "testName-"+i, "testRole-"+i));
                thread.setName("thread-"+i);
                //thread.run();
                thread.start();
            }
    }
    
    private static class WorkRunnable implements Runnable{
        private ThreadLocal<Demo> threadLocal;
        private String name;
        private String role;
        
        public WorkRunnable(ThreadLocal<Demo> threadLocal,  Demo demo, String name, String role) {
            // TODO Auto-generated constructor stub
            this.threadLocal = threadLocal;
            threadLocal.set(demo);
            this.name =name;
            this.role = role;
            System.out.println("thread-name: "+Thread.currentThread()+ "; demo: "+demo);
        }
        @Override
        public void run(){
            Demo demo = threadLocal.get();
            demo.setName(name);
            demo.setRole(role);
            System.out.println("thread name: "+Thread.currentThread()+"; demo: "+ demo+"; demo.name: "+demo.getName()+"; demo.role: "+demo.getRole());
        }
    }

}

测试结果如下:

thread-name: Thread[main,5,main]; demo: Demo@2a139a55
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
Exception in thread "thread-0" Exception in thread "thread-1" java.lang.NullPointerException
    at Test$WorkRunnable.run(Test.java:43)
    at java.lang.Thread.run(Thread.java:745)
java.lang.NullPointerException
    at Test$WorkRunnable.run(Test.java:43)
    at java.lang.Thread.run(Thread.java:745)

通过测试结果发现,若ThreadLocal子类中为实现initialVlaue()方法,则出现.NullPointerException,说明ThreadLocal类为该当前线程未创建本地副本实例对象,因此我们在实际使用过程中,需要根据自己的实际使用情况,实现ThreadLocal类中提供的initialValue()方法创建本地副本对象。

如上所述,若存在遗漏或错误的地方,望大家积极评论指点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值