4-6 ThreadLocal

一、什么是ThreadLocal

java.lang.ThreadLocal,线程本地变量,也叫线程局部变量。。通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种线程隔离机制。对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。
实际开发中真正使用ThreadLocal的场景较少,大多数使用都是在框架里面。最常见的使用场景可以用它来解决数据库连接、Session管理等保证每一个线程中使用的数据库连接是同一个。
二、API简单使用

public class ThreadLocalMain {

    /**
     * 根据Java Doc的建议,ThreadLocal一般声明为static变量(被static修 
    *饰生命周期延长,可以杜绝Entry继承弱引用WeakReference带来的在垃
  *圾回收时会直接删除掉key ThreadLocal对象。但是需要注意的是线程运
*行时间过长,对象过大如果不回收有可能会造成内存溢出。需要及时remove()掉.)
     * withInitial方法,初始化一个有值的threadLocal.
     */
   static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "调用withInitial初始化一个值");
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + " :" + threadLocal.get());
        Runnable r1 = () -> {
            String name = Thread.currentThread().getName();
            threadLocal.set(name + "调用set方法");
            System.out.println("------>" + threadLocal.get());
        };

        Runnable r2 = () -> {
            // 如果初始化ThreadLocal时没有使用withInitial初始化初始值,那么在get()时会返回空。
            String initialVal = threadLocal.get();
            System.out.println("initialVal----------->" + initialVal);
            String name = Thread.currentThread().getName();
            // 线程thread-local-2 执行set方法.
            threadLocal.set(name + "调用set方法!");
            // thread-local-2 get值为当前线程set的值.
            System.out.println("------>" + threadLocal.get());
            // 移除ThreadLocal变量
            threadLocal.remove();
            // threadLocal.get()值为初始化值.
            System.out.println("remove:" + threadLocal.get());
        };

        Thread thread = new Thread(r1, "thread-local-1");
        Thread thread2 = new Thread(r2, "thread-local-2");
        thread.start();
        thread2.start();
    }
}
**********************************************************
main :调用withInitial初始化一个值
------>thread-local-1调用set方法
initialVal----------->调用withInitial初始化一个值
------>thread-local-2调用set方法!
remove:调用withInitial初始化一个值    

三、API源码解析

ThreadLocal API
public T get() {
 // 获取当前线程对象
 Thread t = Thread.currentThread();
 //获取此线程对象中维护的ThreadLocalMap对象
 ThreadLocalMap map = getMap(t);
 // 如果此map存在
 if (map != null) {
 // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
 ThreadLocalMap.Entry e = map.getEntry(this);
 // 找到对应的存储实体 e 
 if (e != null) {
 @SuppressWarnings("unchecked")
 // 获取存储实体 e 对应的 value值
 // 即为我们想要的当前线程对应此ThreadLocal的值
 T result = (T)e.value;
 return result;
 }
 }
 // 如果map不存在,则证明此线程没有维护的ThreadLocalMap对象
 // 调用setInitialValue进行初始化
 return setInitialValue();①
 }
 /**
 * 获取当前线程Thread对应维护的ThreadLocalMap 
 * 
 * @param  t the current thread 当前线程
 * @return the map 对应维护的ThreadLocalMap 
 */
 ThreadLocalMap getMap(Thread t) {
 return t.threadLocals;
 }


 private T setInitialValue() {
 // 调用initialValue获取初始化的值
 T value = initialValue();
 // 获取当前线程对象
 Thread t = Thread.currentThread();
 // 获取此线程对象中维护的ThreadLocalMap对象
 ThreadLocalMap map = getMap(t);
 // 如果此map存在
 if (map != null)
 // 存在则调用map.set设置此实体entry
 map.set(this, value);
 else
 // 1)当前线程Thread 不存在ThreadLocalMap对象
 // 2)则调用createMap进行ThreadLocalMap对象的初始化
 // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
 createMap(t, value);
 return value;
 }

 protected T initialValue() {
 return null;
 }

 /**
 *创建当前线程Thread对应维护的ThreadLocalMap 
 *
 * @param t 当前线程
 * @param firstValue 存放到map中第一个entry的值
 */
 void createMap(Thread t, T firstValue) {
 //这里的this是调用此方法的threadLocal
 t.threadLocals = new ThreadLocalMap(this, firstValue);
 }
/**
 * 设置当前线程对应的ThreadLocal的值
 *
 * @param value 将要保存在当前线程对应的ThreadLocal的值
 */
 public void set(T value) {
 // 获取当前线程对象
 Thread t = Thread.currentThread();
 // 获取此线程对象中维护的ThreadLocalMap对象
 ThreadLocalMap map = getMap(t);
 // 如果此map存在
 if (map != null)
 // 存在则调用map.set设置此实体entry
 map.set(this, value);
 else
 // 1)当前线程Thread 不存在ThreadLocalMap对象
 // 2)则调用createMap进行ThreadLocalMap对象的初始化
 // 3)并将此实体entry作为第一个值存放至ThreadLocalMap中
 createMap(t, value);
 }

 /** ThreadLocal的set最终调用了ThreadLocalMap的set方法 **/
 private void set(ThreadLocal<?> key, Object value) {

 Entry[] tab = table;
 int len = tab.length;
 int i = key.threadLocalHashCode & (len-1);
//哈希码和数组长度求元素放置的位置,即数组下标 从i开始往后一直遍历到数组最后一个Entry
 for (Entry e = tab[i];
 e != null;
 e = tab[i = nextIndex(i, len)]) {
 ThreadLocal<?> k = e.get();
 //如果key相等,覆盖value
 if (k == key) {
 e.value = value;
 return;
 }
//如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
 if (k == null) {
 replaceStaleEntry(key, value, i);
 return;
 }
 }

 tab[i] = new Entry(key, value);
 int sz = ++size;
 //如果超过阀值,就需要再哈希了
 if (!cleanSomeSlots(i, sz) && sz >= threshold)
 rehash();
 }

通过源码我们知道不管是set、get、remove操作的都是ThreadLocalMap,key=ThreadLocal对象,value=线程局部变量缓存值。getMap最终调用的Thread的成员变量 ThreadLocal.ThreadLocalMap threadLocals.

public class Thread implements Runnable {
 /* 与此线程有关的 ThreadLocal 值,该映射由 ThreadLocal 类维护*/
 ThreadLocal.ThreadLocalMap threadLocals = null;

 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
public class ThreadLocal<T> {
 /**ThreadLocalMap是ThreadLocal的一个内部类,ThreadLocalMap是一个定制的哈希映射,
 仅适用于维护线程本地值。ThreadLocalMap类是包私有的,允许在Thread类中声明字段。为了帮助
 处理非常大且长时间的使用,哈希表entry使用了对键的弱引用。有助于GC回收。 **/
 static class ThreadLocalMap {
 static class Entry extends WeakReference<ThreadLocal<?>> {
 /** The value associated with this ThreadLocal. */
 Object value;

 Entry(ThreadLocal<?> k, Object v) {
 super(k);
 value = v;
 }
 }
 }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容