ThreadLocal与InheritableThreadLocal的使用

       ThreadLocal,线程本地存储,为变量在每个线程中都创建了一个副本,那么每个线程可以独立地改变和访问自己的副本变量,而不会影响其它线程所对应的副本变量。从线程的角度看,目标变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。
       ThreadLocal不是用来解决对象共享访问问题的,而是提供了保持对象的方法和避免参数传递的对象访问方式。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象。
       在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个 threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对 Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为 value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

ThreadLocal类的定义:public class ThreadLocal
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
    public T get() {
        Thread t = Thread.currentThread();//取得当前线程
        ThreadLocalMap map = getMap(t);// 获取该线程对应的ThreadLocalMap
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//获取到键值对,注意这里获取键值对传进去的是this,而不是当前线程t。
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法并返回value。
这里的getMap,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
在Thread类中会发现有threadLocals与inheritableThreadLocals两个成员变量,都是ThreadLocal.ThreadLocalMap类。
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
ThreadLocalMap这个类是ThreadLocal类的一个内部类:
public class ThreadLocal {
    。。。。。。
    static class ThreadLocalMap {
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
       。。。。。。
}
可以看出ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。
再看setInitialValue方法:
    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;
    }

    protected T initialValue() {
        return null;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
在setInitialValue方法中,会调用protected修饰的initialValue()方法, 默认情况下,initialValue方法返回的是null。所以在进行get之前,必须先set,否则会报空指针异常,如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

set方法就比较简单了:
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
总的来说,实际上是通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中,threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,这样每个线程中可有多个threadLocal变量。

简单示例:
public class ThreadLocalTest {
    ThreadLocal a = new ThreadLocal<>();
    ThreadLocal b = new ThreadLocal() {
        protected String initialValue() {
            return Thread.currentThread().getName() + "---zero";
        }
    };

    public static void main(String[] args) throws InterruptedException {
        final ThreadLocalTest test = new ThreadLocalTest();

        System.out.println(test.a.get());
        System.out.println(test.b.get());

        Thread thread1 = new Thread() {
            public void run() {
                test.a.set(new StringBuilder("hello"));
                System.out.println(test.a.get());
                System.out.println(test.b.get());
            };
        };
        thread1.start();
        thread1.join();

        System.out.println(test.a.get());
        System.out.println(test.b.get());
    }
}
运行结果:
null
main---zero
hello
Thread-0---zero
null
main---zero


       InheritableThreadLocal类继承于ThreadLocal类,所以它具有ThreadLocal类的特性,但又是一种特殊的 ThreadLocal,其特殊性在于InheritableThreadLocal变量值会自动传递给所有子线程,即在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值。而普通ThreadLocal变量不行。

       如果一个子线程调用InheritableThreadLocal的get(),那么它将与它的父线程看到同一个对象。为保护线程安全性,应该只对不可变对象(一旦创建,其状态就永远不会被改变的对象)使用InheritableThreadLocal,因为对象被多个线程共享。

简单示例:

public class InheritableThreadLocalTest {
    InheritableThreadLocal a = new InheritableThreadLocal<>();
    InheritableThreadLocal b = new InheritableThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        final InheritableThreadLocalTest test = new InheritableThreadLocalTest();

        test.a.set(new StringBuilder("main---hello"));
        test.b.set("main---zero");
        System.out.println(test.a.get());
        System.out.println(test.b.get());

        Thread thread1 = new Thread() {
            public void run() {
                System.out.println(test.a.get());
                System.out.println(test.b.get());
                StringBuilder a = test.a.get().append("---subThread");
                test.a.set(a);
                test.b.set("subThread--zero");
                System.out.println(test.a.get());
                System.out.println(test.b.get());
            };
        };
        thread1.start();
        thread1.join();

        System.out.println(test.a.get());
        System.out.println(test.b.get());
    }
}
运行结果:
main---hello
main---zero
main---hello
main---zero
main---hello---subThread
subThread--zero
main---hello---subThread
main---zero
可以看出,如果InheritableThreadLocal存储的是可变性(mutable)的对象,如StringBuilder,对于主线程设置的值子线程可以通过get函数获取,但子线程调用set函数设置新值后,对主线程没有影响,对其它子线程也没有影响,只对自己可见,但如果子线程先get获取再修改对象的属性,那么这个修改对主线程和其它子线程是可见的,因为共享的是同一个引用。为了保护线程的安全性,一般建议只传递不可变(Immuable)对象,即没有状态的对象。


你可能感兴趣的:(#,【ThreadLocal】)