ThreadLocal原理分析

ThreadLocal即线程变量,下面是官方文档中的说明:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
这个类提供线程局部变量。这些变量不同于它们的正常副本,因为访问一个线程的每个线程(通过它的get或set方法)都有其自己的,独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类(例如用户ID或事务ID)中的私有静态字段。
只要线程处于活动状态并且ThreadLocal实例可以访问,每个线程就拥有对其线程局部变量副本的隐式引用;在线程消失后,线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)。

上面的说明已经说的很清楚了,ThreadLocal这个类的作用是提供线程内的局部变量,用于访问,这个变量只在某个线程的生命周期内起作用,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。
ThreadLocal并不是解决了多线程环境下的并发问题,为什么,在这里先说一下,带着疑问接着往下读。ThreadLocal中的set方法存进去的对象是本线程自己使用的对象,其他的线程根本不需要访问,更访问不到,根本没有涉及到并发的问题,自己使用自己的数据,各司其职,互不干扰。


ThreadLocal类:

通过看ThreadLocal类中的源码,可以分析工作流程

构造方法:

只是创建线程局部变量

    /**
     * Creates a thread local variable.
     */
    public ThreadLocal() {
    }

initialValue方法:

内部并没有实现具体的功能,用protected权限修饰符修饰,也就是只能在子类实现具体的功能。该方法实现的是:返回此线程局部变量的当前线程的“初始值”。 该方法将在第一次使用get()方法访问变量时被调用,除非线程先前调用了set(T)方法,在这种情况下, initialValue方法将不会被调用。 通常情况下,这种方法最多每个线程调用一次,但它可能会再次在以后的调用的情况下调用remove()其次是get()

    protected T initialValue() {
        return null;
    }

get方法:

返回当前线程的此线程局部变量的副本中的值。 如果变量没有当前线程的值,则首先将其初始化为调用initialValue()方法返回的值(通过setInitialValue()方法调用)。

public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //调用getMap方法返回的是ThreadLocalMap类型的map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //获取当前对象对应的值,注意这里是this不是上面的t
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                //返回这个键值对的值
                return (T)e.value;
        }
        //如果为空返回初始值
        return setInitialValue();
    }

仔细分析一下get方法:ThreadLocalMap map = getMap(t);返回的是ThreadLocalMap类型的对象(根据当前线程获取map),ThreadLocalMap是ThreadLocal的一个静态内部类,他的内部还维持着一个静态内部类Entry,一个非常熟悉的内部类,在HashMap中也是它。Entry以键值对的形式存储着内容,这里存的键是ThreadLocal类型的对象。
根据上面的源码我们还需要看getMap()方法的实现;

    ThreadLocalMap getMap(Thread t) {
        //返回当前线程的threadLocals
        return t.threadLocals;
    }

这个threadLocals其实是在上面我们说到的ThreadLocal的内部类ThreadLocalMap

ThreadLocal.ThreadLocalMap threadLocals = null;

接下来分析setInitialValue()方法:

private T setInitialValue() {
        //获取初始值
        T value = initialValue();
        //获得当前线程
        Thread t = Thread.currentThread();
        //根据当前线程获取ThreadLocalMap对象map
        ThreadLocalMap map = getMap(t);
        //判断map不为空把当前对象和获得的值添加进入
        if (map != null)
            map.set(this, value);
        else
            //如果为空创建一个新的ThreadLocalMap
            createMap(t, value);
        return value;
    }

createMap方法:

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

总结:每个Thread维护一个ThreadLocalMap映射表(一个线程一个map),这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。

set方法:

将当前线程的此线程局部变量的副本设置为指定的值。 大多数子类将无需重写此方法,仅依靠initialValue()方法设置线程本地值的值。
看了上面的分析这里的set方法的代码也比较好看了

public void set(T value) {
        Thread t = Thread.currentThread();
        //根据当前线程获取ThreadLocalMap对象map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map

Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//根据当前线程获得一个map

执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。


ThreadLocal例子:

package threadtest;

/**
 * Created with IDEA
 *
 * @author duzhentong
 * @Date 2018/4/9
 * @Time 15:31
 */
public class ThreadLocalTest {
    private static final ThreadLocal<Integer> threadlocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new MyThread(i)).start();
        }
    }

    static class MyThread implements Runnable {

        private int id;

        public MyThread(int id) {
            this.id = id;
        }
        @Override
        public void run() {
            System.out.println("线程" + id + "的初始值是:" + threadlocal.get());
            for (int i = 0; i < 10; i++) {
                threadlocal.set(threadlocal.get() + i);
            }
            System.out.println("线程" + id + "的累加值是:" + threadlocal.get());
        }
    }
}

输出结果(每次都会不同)

线程2的初始值是:0
线程4的初始值是:0
线程3的初始值是:0
线程4的累加值是:45
线程2的累加值是:45
线程3的累加值是:45
线程0的初始值是:0
线程0的累加值是:45
线程1的初始值是:0
线程1的累加值是:45

从上面的输出结果看,每个线程中的值都是独立的,与其他线程没有任何关系。

猜你喜欢

转载自blog.csdn.net/qq_38663729/article/details/79867856
今日推荐