ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不一样的 Thread 中有不一样的副本。这里有几点须要注意:html
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每一个使用该变量的线程都会初始化一个彻底独立的实例副本。ThreadLocal 变量一般被private static修饰。当一个线程结束时,它所使用的全部 ThreadLocal 相对的实例副本均可被回收。java
总的来讲,ThreadLocal 适用于每一个线程须要本身独立的实例且该实例须要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。api
首先 ThreadLocal 是一个泛型类,保证能够接受任何类型的对象。数组
由于一个线程内能够存在多个 ThreadLocal 对象,因此实际上是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫作 ThreadLocalMap 的静态内部类。而咱们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。例以下面的 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); }
get方法:多线程
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) return (T)map.get(this); // Maps are constructed lazily. if the map for this thread // doesn't exist, create it, with this ThreadLocal and its // initial value as its only entry. T value = initialValue(); createMap(t, value); return value; }
createMap方法:oracle
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap是个静态的内部类:app
static class ThreadLocalMap { ........ }
最终的变量是放在了当前线程的 ThreadLocalMap
中,并非存在 ThreadLocal 上,ThreadLocal 能够理解为只是ThreadLocalMap的封装,传递了变量值。ide
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特色是,若是这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。this
因此若是 ThreadLocal 没有被外部强引用的状况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。可是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。
ThreadLocalMap实现中已经考虑了这种状况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。
若是说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,而且以后也再也不调用 get()、set()、remove() 方法的状况下。因此一般在调用get()方法后,便可手机调用一下remove()方法。
如上文所述,ThreadLocal 适用于以下两种场景
对于第一点,每一个线程拥有本身实例,实现它的方式不少。例如能够在线程内部构建一个单独的实例。ThreadLoca 能够以很是方便的形式知足该需求。
对于第二点,能够在知足第一点(每一个线程有本身的实例)的条件下,经过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
一个简单的用ThreadLocal来存储Session的例子:
private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s; }
好比Java7中的SimpleDateFormat不是线程安全的,能够用ThreadLocal来解决这个问题:
public class DateUtil { private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } }; public static String formatDate(Date date) { return format1.get().format(date); } }
这里的DateUtil.formatDate()就是线程安全的了。(Java8里的 [java.time.format.DateTimeFormatter](http://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html)
是线程安全的,Joda time里的DateTimeFormat也是线程安全的)。
既然ThreadLocal
用map就避免不了冲突的产生
这里碰撞其实有两种类型
只有一个ThreadLocal实例的时候(上面推荐的作法),当向thread-local变量中设置多个值的时产生的碰撞,碰撞解决是经过开放定址法, 且是线性探测(linear-probe)
多个ThreadLocal实例的时候,最极端的是每一个线程都new
一个ThreadLocal实例,此时利用特殊的哈希码0x61c88647
大大下降碰撞的概率, 同时利用开放定址法处理碰撞
注意 0x61c88647
的利用主要是为了多个ThreadLocal
实例的状况下用的
从ThreadLocal
源码中找出这个哈希码所在的地方
private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
注意实例变量threadLocalHashCode
, 每当建立ThreadLocal
实例时这个值都会累加 0x61c88647
, 目的在上面的注释中已经写的很清楚了:为了让哈希码能均匀的分布在2的N次方的数组里, 即 Entry[] table
下面来看一下ThreadLocal
怎么使用的这个 threadLocalHashCode
哈希码的,下面是ThreadLocalMap
静态内部类中的set方法的部分代码:
private Entry[] table; private void set(ThreadLocal<?> key, Object value) { 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(); }
key.threadLocalHashCode & (len-1)
这么用是什么意思? 先看一下table
数组的长度吧:
ThreadLocalMap中 Entry[] table
的大小(默认为16,resize:*2)必须是2的N次方呀(len = 2^N),那 len-1
的二进制表示就是低位连续的N个1, 那 key.threadLocalHashCode & (len-1)
的值就是 threadLocalHashCode
的低N位, 这样就能均匀的产生均匀的分布? 我
这与fibonacci hashing(斐波那契散列法)以及黄金分割有关,具体可研究中的 6.4 节Hashing部分
参考:
http://www.jasongj.com/java/threadlocal/