ThreadLocal

Posted zzm96

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal相关的知识,希望对你有一定的参考价值。

1. 是什么?

threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,官方解释如下

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code 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).
 */

大致意思就是ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

做个不恰当的比喻,从表面上看ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。

这里的这个比喻是不恰当的,实际上是ThreadLocal的静态内部类ThreadLocalMap为每个Thread都维护了一个数组table,ThreadLocal确定了一个数组下标,而这个下标就是value存储的对应位置。

2.  怎么用?

测试案例
    public void test1() {
        ThreadLocal<String> tl1 = new ThreadLocal<>();
        ThreadLocal<String> tl2 = new ThreadLocal<>();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                tl1.set(Thread.currentThread().getName()+"->TL1");
                tl2.set(Thread.currentThread().getName()+"->TL2");
                System.out.println(Thread.currentThread().getName()+"   "+tl1.get());
                System.out.println(Thread.currentThread().getName()+"   "+tl2.get());

                tl1.remove();
                tl2.remove();

            }, "thread"+i).start();
        }
    }

//打印结果
thread1   thread1->TL1
thread1   thread1->TL2
thread0   thread0->TL1
thread0   thread0->TL2
thread2   thread2->TL1
thread3   thread3->TL1
thread4   thread4->TL1
thread4   thread4->TL2
thread2   thread2->TL2
thread3   thread3->TL2
分析:每个ThreadLocal实例为每个线程维护一个变量(各线程间独立),get/set/remove  对当前线程的值进行操作

相关操作源码

 1      public T get() {
 2         Thread t = Thread.currentThread();
 3         ThreadLocalMap map = getMap(t); // 该方法返回线程的 threadlocals(下文)
 4         if (map != null) {
 5             ThreadLocalMap.Entry e = map.getEntry(this);
 6             if (e != null) {
 7                 @SuppressWarnings("unchecked")
 8                 T result = (T)e.value;
 9                 return result;
10             }
11         }
12         return setInitialValue();
13     }
14 
15     public void set(T value) {
16         Thread t = Thread.currentThread();
17         ThreadLocalMap map = getMap(t);
18         if (map != null)
19             map.set(this, value);
20         else
21             createMap(t, value);
22     }
23 
24      public void remove() {
25          ThreadLocalMap m = getMap(Thread.currentThread());
26          if (m != null)
27              m.remove(this);
28      }

3. 为什么?

  • 每个Thread有一个初始值为null的 ThreadLocalMap 实例,初始化时 key 为当前线程,value为存储的值。 即所有的操作都是针对当前线程的 threadlocals.

技术图片

技术图片

  • ThreadLocalMap 为 ThreadLocal 的静态内部类, ThreadLocalMap 中有 Entry 数组,Entry 为最终存储值的结构,之所以用数组是因为有多个 ThreadLocal, 利用Hash 在Entry中存储不同ThreadLocal 对应的value

技术图片

 

白话:每个线程有个map(ThreadLocalMap), 这个map 有个数组(Entry),数组利用Hash进行地址索引,对应不同的 ThreadLocal

技术图片

 

注意: Entry继承了WeakRefrence(如上代码),下次GC后  key(ThreadLocal)就没了,如果线程没有销毁,value还在,就有内存泄露的情况。

  所以一般用 try{} finally{ delete()} 的结构

  set方法本身有优化,判断当前弱引用(ThreadLocal)是否为空,为空则替换

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal key, Object value) {

            // We don‘t use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

 4. 用在哪儿

多个方法嵌套调用相同参数,单线程中用 static 修饰变量即可,多线程会出问题,此时用 ThreadLocal 可实现线程间变量的隔离。技术图片

 

 

 

 

 

参考:

https://www.cnblogs.com/yinbiao/p/10728909.html

https://www.jianshu.com/p/3c5d7f09dfbd

https://www.jianshu.com/p/1a5d288bdaee

https://www.bilibili.com/video/BV1SJ41157oF?from=search&seid=1014931581670395880

以上是关于ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

源代码系列02——ThreadLocal源码分析(基础篇)

java中的ThreadLocal详解及示例代码

ThreadLocal介绍

MyBatis基础:使用java提供的ThreadLocal类优化代码

Java 单线程代码ThreadLocal串值问题

ThreadLocal