ThreadLocal的概念
ThreadLocal从英文的角度看,可以看成thread和local的组合,就是线程本地的意思,我们都知道,看过jvm内存分配的人都知道在jvm虚拟机中对每一个线程都分配了一个独立的空间。独立的空间就意味着线程之间是相互隔离的。那么如果在独立空间里面声明某一些东西(也就是线程内部的东西),这个就可以变向的解决多线程程序并发问题了(个人理解)。这个东西就是ThreadLocal,所以说ThreadLocal可以为解决多线程的并发问题提供一种新的思路。稍后threadLocal使用一节会讲解。学过java的人都知道ThreadLocal变量是为使用它的线程提供一个单独的线程局部变量值的副本。所以每个线程都可以独立的改变自己的副本而不会影响其他线程所对应的副本。
说明:在ThreadLocal源码的时候,有一个注释我在这里引用下,如下图。翻译过来就是:该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).
从上面的话中我们可以得出以下几点信息
(1) ThreadLocal是一个线程局部的变量。
(2) ThreadLocal 独立于变量的初始化副本,在看ThreadLocal源码的时候会有一个initialValue(),也就是说ThreadLocal可以初始化一个值,然后某个线程可以获取到这个初始化值的副本。
(3) 状态和某一个线程关联,因为ThreadLocal不是是用于共享变量所设计的,而且为了方便线程处理自己的状态而引入的。
Thread关于ThreadLocal讲解
java线程知识太多了,本文只是谈谈ThreadLocal理解,在Thread.java代码中,我们可以看到声明了两个关于ThreadLocal.ThreaLocalMap的变量,ThreadLocalMap主要是用来存放local变量的,以后每个Thread要访问local对象的时候,那么就会使用这两个声明的变量。很多人会问,为什么Thread里面会引用两个ThreadLocalMap对象呢,这两个对象又有什么区别呢?这个问题留给自己去想想,我就不在这里说了,看看字面意思应该就明白了。
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
ThreadLocal源码解析:
(1) 构造函数
/** * Creates a thread local variable. */ public ThreadLocal() { }
(2) initialValue 初始化值
这个是一个初始化值的函数,是保护类型的,很明显,作者的意图是想要重载此函数。基本情况下,此函数只会调用一次。在调用第一次调用get方法的时候会调用这个方法。
protected T initialValue() {
return null;
}
具体用户如下:
private static final ThreadLocal<DateFormat> dataFormat = new ThreadLocal<DateFormat>(){ protected DateFormat initialValue() { // 初始化值 return new SimpleDateFormat("yyyy-HH-MM"); } };
(2) set方法
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
调用set方法的时候会首先调用getMap来获取一个ThreadLocalMap,还记得在前面说的Thread类里面声明了两个变量吗?这里很巧妙的是,ThreadLocalMap就是获取当前线程里面的声明的ThreadLocalMap,所以说ThreadLocal是线程的局部变量。如果
map为空,当前会创建一个map。
(3)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(); } /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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方法没有什么讲的,就是设置而已。
(4) remove方法
/** * Remove the entry for key. */ 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; } } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
remove它提供了移除此线程局部变量在当前进程的值,在ThreadLocalMap的remove方法会调用expungeStaleEntry的方法,这个方法作用很大,它会把ThreadLocalMap里面保存的key为空的值置空。方便GC处理。所以说在很多人都说在ThreadLocal保存的值在不使用的情况下,最好调用remove方法。
ThreadLocal可能引起的内存泄露
ThreadLocal其实里面存放线程局部变量的是ThreadLocalMap,ThreadLocalMap是一个什么东西呢?字面意思就可以看出是一个map。它主要是用来存放local信息的,我们存放的时候就是以ThreadLocal为key来进行存放的。从源码中可以看出,key是一个弱引用类型。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。下面我们来证明下,请看下面代码。
package com.zh.test; import java.lang.ref.WeakReference; import com.zh.test.MyThreadLocal.ThreadLocalMap; public class Test1 { public static void main(String[] args) { MyThreadLocal myThreadLocal = new MyThreadLocal(); myThreadLocal.set(new Object()); ThreadLocalMap threadLocalMap = myThreadLocal.getThreadLocalMap(); threadLocalMap.printAll(); myThreadLocal = null; System.gc(); threadLocalMap.printAll(); } } class MyThreadLocal{ static ThreadLocalMap threadLocalMap = new ThreadLocalMap(); public void set(Object vData){ threadLocalMap.set(this, vData); } public ThreadLocalMap getThreadLocalMap(){ return threadLocalMap; } static class ThreadLocalMap { private Entry[] table = new Entry[1]; static class Entry extends WeakReference<MyThreadLocal> { Object value; Entry(MyThreadLocal k, Object v) { super(k); value = v; } } private void set(MyThreadLocal key, Object value) { table[0] = new Entry(key, value); } public void printAll(){ for (Entry entry : table) { System.out.println(entry.get()); System.out.println(entry.value); } } } }
运行上面代码,会发现,在运行GC以前没有任何变化,在运行GC以后,发现key为空了,但是value值却还存在,这说明直接将threadLocal置空,这个方法是不正确的,这个会引起内存泄露的。所以说千万不要把ThreadLocal置空,那么应该如何处理呢,在前面已经说了,在不用了ThreadLocal的时候直接调用remove方法。这样的话内部会把key为空的value值也清楚了。
ThreadLocal的使用
ThreadLocal如何使用呢,在ThreadLocal的源码注释中,就交你如何使用了,本文就不交代了,有时间看看源码,会有意想不到的收获。
ps:可以把一些线程不安全的东西放在ThreadLocal里面比如说dateFormat,数据库连接。。。。。。。