想必你们都对Threadlocal很熟悉吧,今天咱们就一块儿来深刻学习一下。Threadlocal我更倾向于将其翻译成线程局部变量。它有什么用处呢?Threadlocal对象一般用于防止对可变的单实例变量或全局变量进行共享。在spring中,经过将事务上下文保存在静态的threadlocal中,当框架代码须要判断当前运行的是哪个事务时,只须要从ThreadLocal对象中获取事务上下文,这种机制很方便,避免了在每一个方法都要传递上下文信息。html
众所周知,SimpleDateFormat不是一个线程安全的类,在多线程环境下使用同一个实例是不正确的。经过将SimpleDateFormat保存在Threadlocal中,每一个线程都会拥有属于本身的SimpleDateFormat,代码以下所示:面试
public class DateFormatUtil { public static ThreadLocal<SimpleDateFormat> dataFormatlocal = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(); } }; public static SimpleDateFormat getInstance() { return dataFormatlocal.get(); } }
而后你就能够安心地调用了,不用考虑线程安全问题。固然你也能够在每一个线程内部调用new SimpleDateFormat(),但现实状况是大部分开发可能并不知道它是线程不安全的,而且将其封装成一个单例的工具类。至于如何使用,全凭各位读者爱好,这个例子主要用来演示Threadloca的基本用法。spring
经过以前的演示,咱们已经大体知道threadlocal怎么用,而且清楚threadlocal保证了每一个线程都有一个局部变量副本。若是以此为需求,让咱们本身去设计,会是什么样子的呢?api
static Map<String,Object> threadlocal = new HashMap<String,Object>();
这就是我设计的....好吧(╯▽╰),原谅我水平有限....在这个hashMap中,threadId为key,Object为value,也是能够实现Threadlocal的。那么咱们如今要来考虑几个问题。jdk是这样设计的吗?这样设计很low,可是low在哪了?
答:大师们固然不是这样设计了。若是这样设计,每一个线程的变量都会永久的保存在hashMap中,存在内存泄漏。安全
low的写法咱们已经见过了,如今咱们一块儿来分下jdk是如何设计的,本文引用jdk1.8。
让咱们看下threadlocal的结构图:数据结构
类核心方法set、get、initialValue、setInitialValue、remove,后面主要围绕着这几个方法介绍。多线程
类核心变量threadLocalHashCode,nextHashCode,HASH_INCREMENT,其中nextHashCode和HASH_INCREMENT都是静态的,因此对于一个threadlocal对象,成员变量只有一个threadLocalHashCode,这是一个自定义的hash函数,主要为了减小散列桶的冲突(不是本文重点,好奇的能够看下这篇博客https://www.cnblogs.com/ilell...)。框架
那到底Threadlocal将变量存到哪了呢?眼尖的同窗确定已经发现了ThreadLocalMap,再来看下ThreadlocalMap的结构:ide
相信读过hashMap源码同窗的必定会以为很是眼熟,ThreadlocalMap的主体也是Entry[],有resize()和rehash(),区别仅仅就是在于没有使用链表结构和红黑树来处理散列冲突了。Entry的结构咱们也颇有必要了解一下:函数
Entry继承了一个弱引用,这里简单介绍下弱引用的知识。弱引用是用来描述非必需对象的,被弱引用关联的对象只能生存到下次垃圾收集发生以前。不管当前内存是否足够,都会回收掉只被弱引用关联的对象。不少面试官都喜欢问Threadlocal是怎么发生内存泄漏的,其实就是在问这个,这个须要咱们对Threadlocal有一个总体的了解才能明白,因此放在最后讲。
当咱们看完Threadlocal的结构了,咱们发现ThreadlocalMap是用来存储的数据结构,那它是怎么和线程关联起来的呢?这里咱们开始研究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); }
步骤1:获取当前线程
步骤2:经过当前线程获取ThreadLocalMap
步骤3:若是map不为null,将当前线程和value放到map中,不然建立一个map。
如何和线程绑定的玄机就在getMap(t)中。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }
看到这里,咱们就知道了原来Threadlocal有一个内部类ThreadlocalMap,对于任何一个线程都会有惟一的一个ThreadlocalMap来对应,而这个map实际并不存储在Threadlocal中,而是存在Thread当中,只不过由Threadlocal暴露了一套api来维护Thread的ThreadLocalMap。这样设计的好处就是,当线程死掉以后,ThreadLocalMap没有强引用,方便收集器回收。
继续来看get()
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
若是前面的流程看懂了,这就很简单了。当ThreadLocalMap为null或者Entry为null的时候将会调用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()初始化一个值,获取当前线程的ThreadLocalMap,后面的流程以前都已经介绍过了,这里再也不重复,咱们主要来看下initialValue()
protected T initialValue() { return null; }
请注意,protected修饰,return null;这是一个初始化值得方法,也就意味着若是业务容许的话,须要咱们本身实现initialValue();
ThreadLocal的remove实现
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocalMap的remove实现
private void remove(ThreadLocal key) { 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)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
都很是简单,就留给你们本身看了~。~.
先回顾一下ThreadlocalMap的结构
static class ThreadLocalMap { /** * 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; } } ..... ..... }
而后套用网上的一张图(实线表示强引用,虚线表示虚引用)
ThreadLocalMap使用ThreadLocal的弱引用做为key,若是一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,若是当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:ThreadLocal Ref -> Thread -> ThreaLocalMap -> Entry -> value永远没法回收,形成内存泄露。其实jdk已经考虑到了这种状况,ThreadLocalMap的genEntry函数或者set函数会去遍历将key为null的给移除掉,但这明显不是全部状况都成立的,因此须要调用者本身去调用remove函数,手动删除掉须要的threadlocal,防止内存泄露。而后jdk并不建议在栈内声明threadlocal,而是建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,因为一直存在ThreadLocal的强引用,因此ThreadLocal也就不会被回收,也就能保证任什么时候候都能根据ThreadLocal的弱引用访问到Entry的value值,而后remove它,防止内存泄露。
放图证实这是jdk说的~~
再不睡觉,就天明了..