ThreadLocal学习

  该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

  我们从中可以看出几点

  1.每个线程都有自己的独立变量

  每个线程都有独立于其它线程的上下文来保存这个变量,这个变量是私有的,一个线程的本地变量对其它变量是不可见的(有前提)。

  2.独立于变量的初始版本

  ThreadLocal可以给一个初始值,而每个线程都会获得这个初始值的一个副本,这样才能保证不同的线程都有一份拷贝

  3.状态与某一个线程相关联

  ThreadLocal方便每个线程处理自己的状态,解决共享变量问题的而引入的机制

  下面来看个demo

  

private static ThreadLocal<Integer> i=new ThreadLocal<Integer>();
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        set();get();
        Thread t=new Thread(new Runnable() {
                @Override
                public void run() {
                    set();get();
                }
            },"Thread-1");
        t.start();
        try {t.join();//join方法是为了让主线程阻塞等待子线程执行完后再来回到主线程上执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        get();
    }
    static void set(){
        i.set((int)Thread.currentThread().getId());
    }
    static void get(){
        System.out.println(Thread.currentThread().getName()+"=="+i.get());
    }

  输出结果是:

  

main==1
  Thread-1==11
  main==1

  从输出结果中可以看出主线程和子线程中的变量是没有互相干扰的。

  让我们先来看看ThreadLocal的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);
    }
再来让我们看看getMap()方法
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
ThreadLocal.ThreadLocalMap threadLocals = null;

通过源码可以看出threadLocal内部有个内部静态类的变量且初始值为null;

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

刚开始是线程内部自己创建了个ThreadLocalMap对象,而且插入了一个值key是ThreadLocal对象(this就是调用createMap那个对象),value是设置的值。

再来看看get()方法

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();
    }

从getMap(t)这个方法获取到本线程的ThreadLocalMap对象,由于开始我们set()过值,所以得到的map不为空,然后就能得到我们开始设置的变量副本了。

然而当map为空的时候呢?(map为空的时候就是我们开始没有调用set()方法,而是先调用了get()方法)

内部会调用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;
    }

再来看看initialValue()方法

protected T initialValue() {
        return null;
    }

此方法的默认实现是返回null,而且看它的方法的修饰符protected显然是为子类方便继承后重写而特意设置的。

故如果我们没有先set()而get()时要先重写initialValue()方法,否则有可能会报空指针异常。

private static ThreadLocal<Integer> i=new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 666;
        }
    };
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        set();get();
        Thread t=new Thread(new Runnable() {
                @Override
                public void run() {
                    set();get();
                }
            },"Thread-1");
        t.start();
        try {t.join();//join方法是为了让主线程阻塞等待子线程执行完后再来回到主线程上执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        get();
    }
    static void set(){
        i.set((int)Thread.currentThread().getId());
    }
    static void get(){
        System.out.println(Thread.currentThread().getName()+"=="+i.get());
    }

输出结果是:

main==666
Thread-1==11
main==666

个人总结:ThreadLocal可以解决多线程下共享变量带来的问题,由线程内部自己的ThreadLocalMap来存储维护以TreadLocal对象为key的集合(key是弱引用,此方面以后再另行讨论)就足以看出设计者的用心良苦,可以方便的实现一个线程拥有多个自己的变量副本,ThreadLocal的使用场景方面,在获取数据库连接的时候使用ThreadLocal等等。。。

多个变量副本demo

private static ThreadLocal<String> s=new ThreadLocal<String>();
    private static ThreadLocal<Integer> i=new ThreadLocal<Integer>();
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        set();get();
        Thread t=new Thread(new Runnable() {
                @Override
                public void run() {
                    set();get();
                }
            },"Thread-1");
        t.start();
        try {t.join();//join方法是为了让主线程阻塞等待子线程执行完后再来回到主线程上执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        get();
    }
    static void set(){
        s.set(Thread.currentThread().getName());
        i.set((int)Thread.currentThread().getId());
    }
    static void get(){
        System.out.println(s.get()+"=="+i.get());
    }

运行结果:

main==1
Thread-1==11
main==1

注:有部分内容参考自https://my.oschina.net/clopopo/blog/149368

原文地址:https://www.cnblogs.com/wuyouwei/p/6155332.html