ThreadLocal作用以及原理解析

ThreadLocal作用

对于多个线程访问一个共享变量的时候,我们往往要通过加锁的方式进行同步,像这样

但是除此之外,其实还有另一种方式可以隔绝线程对于共享变量读写的独立性。那就是ThreadLocal。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有一块独立的空间,当多个线程操作这个变量的时候,实际上操作的都是自己线程所属的空间的那个变量,不会对其他线程有影响,也不会被其他线程影响,因为彼此都是互相独立的。因此想要保证线程安全,也可以把共享变量放在ThreadLocal中。总体来说就是,ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

接下来看一个例子

public class ThreadLocalDemo1 {

    public static int value = 0;
   static ThreadLocal<Object> local = new ThreadLocal<>();
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"---线程初始值:"+local.get());
                    local.set("我是"+Thread.currentThread().getName());
                    System.out.println(Thread.currentThread().getName()+"---线程修改值:"+local.get());
                }
            },"线程"+i).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程"+local.get());
    }

}

运行结果

线程1---线程初始值:null
线程4---线程初始值:null
线程3---线程初始值:null
线程0---线程初始值:null
线程2---线程初始值:null
线程0---线程修改值:我是线程0
线程3---线程修改值:我是线程3
线程4---线程修改值:我是线程4
线程1---线程修改值:我是线程1
线程2---线程修改值:我是线程2
主线程null

上面这段代码,运行结果印证了我们开头说的那些关于ThreadLocal的论述,分析一下代码,可以看到,我们这里只有一个ThreadLocal对象,即local,我们一共有五个线程,线程0对local进行set值之后,线程2再get却还是null,但是线程0自己再get,却可以拿到自己设置的那个值 我是线程0 ,别的线程是拿不到这个值的,而且代码的最后,在所有的线程都运行完毕之后,在主线程对local进行get操作,拿到的值却还是null。这就印证了这个结论,每个线程都只能拿到自己线程所属的值,线程之间是相互独立的

ThreadLocal原理

set方法

话不多说,我们直接上源码。首先先看看ThreadLocal的set方法

public void set(T value) {
    //先获取当前线程
        Thread t = Thread.currentThread();
    //看看当前线程是不是已经设置过值了
        ThreadLocalMap map = getMap(t);
    //如果设置过了,就覆盖原来的值
        if (map != null)
            map.set(this, value);
        else
            //如果当前线程第一次设置值 那就创建一个存储区域(Map)
            createMap(t, value);
    }

继续看看 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);
    }

根据注释,和源码里面可以看到,createMap方法里面实际上就是new了一个ThreadLocalMap对象,创建了一个与ThreadLocal关联的map,然后把当前Thread中的一个ThreadLocal引用和要设置的值放进去。请注意t.threadLocals是定义在Thread类里的,代码如下

  //这一段是定义在Thread类中的
ThreadLocal.ThreadLocalMap threadLocals = null;

继续看看ThreadLocalMap里有啥

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

可以看到,ThreadLocalMap里维护了一个Entry数组,将Thread里面的ThreadLocal.ThreadLocalMap变量引用并且通过一系列的hashcode操作,作为key。接下来继续看看Entry

    /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁导致ThreadLocal对象无法被回收。

以上就差不多是整个set方法的源码了,有了以上的了解,再看get的就会很简单

get方法

  public T get() {
      //获取当前线程,并以线程引用为key去ThreadLocalMap中获取值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
      //如果为不为空,就以当前线程对应的Thread中的ThreadLocal引用 进行hash计算 拿到值返回
        if (map != null) {
            //拿到引用去获取值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
      //为空就返回一个初始值
        return setInitialValue();
    }

//对应getMap方法 返回的是当前线程的threadLocals引用
  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
//上面的getEntry方法
      private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

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


可以看到,如果你一个线程在get之前没有set,也是会给你以当前线程创建一个与你线程对应的createMap的,只不过key是当前线程下的ThreadLocalMap引用,value为null,因为

 T value = initialValue();

这一行的的initialValue()方法是这样的

  protected T initialValue() {
        return null;
    }

其实还有remove方法啥的,原理都差不多.。所以就不赘述了。

总结

ThreadLocal为每个线程都定义了一个ThreadLocalMap类型名为threadLocals的变量,有点类似于HashMap,但是严格意义上来说不是,只是将其类比为Map结构,key为当前线程中的threadLocals的this引用,value是设置的值。所以每个线程都有不同的key,所能获取到的值也是不一样的,就是利用这种思想去保证值对每个线程的独立性。因为不管是get还是set之前都会有currentThread这个操作,所以每个线程都只能取到自己线程对应的值。

posted @ 2020-05-20 14:15  穿黑风衣的牛奶  阅读(1316)  评论(0编辑  收藏  举报