ThreadLocal介绍
ThreadLocal,顾名思义,线程局部变量。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
我们可以把它看作是一个改装过的一个类,我们假设现在有一个这个类的公共实例变量,有好几个线程它们都能访问使用这个变量。这个变量有以下几个常用的方法:
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
好的现在假设各个线程都给这个变量 set了一个自己的int数字,如果是普通的变量,正常的逻辑应该是:哪个线程是最后一个设置它的,这个变量的值就是哪个。
但现在这个改装过的不一样,虽然各个线程都可以操作这个变量,但对各个线程来说,它其实是逻辑上独立的。换句话说,即使刚刚各个线程都给它set了,但每个线程get的时候,拿到的还是自己本来set的那个int值。
看个例子来理解吧:
public class MyThreadLocal { private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){ /** * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值 */ @Override protected Object initialValue() { System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!"); return null; } }; public static void main(String[] args) { new Thread(new MyIntegerTask("IntegerTask1")).start(); new Thread(new MyStringTask("StringTask1")).start(); new Thread(new MyIntegerTask("IntegerTask2")).start(); new Thread(new MyStringTask("StringTask2")).start(); } public static class MyIntegerTask implements Runnable { private String name; MyIntegerTask(String name) { this.name = name; } @Override public void run() { for(int i = 0; i < 5; i++) { // ThreadLocal.get方法获取线程变量 if(null == MyThreadLocal.threadLocal.get()) { // ThreadLocal.et方法设置线程变量 MyThreadLocal.threadLocal.set(0); System.out.println("线程" + name + ": 0"); } else { int num = (Integer)MyThreadLocal.threadLocal.get(); MyThreadLocal.threadLocal.set(num + 1); System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get()); if(i == 3) { MyThreadLocal.threadLocal.remove(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static class MyStringTask implements Runnable { private String name; MyStringTask(String name) { this.name = name; } @Override public void run() { for(int i = 0; i < 5; i++) { if(null == MyThreadLocal.threadLocal.get()) { MyThreadLocal.threadLocal.set("a"); System.out.println("线程" + name + ": a"); } else { String str = (String)MyThreadLocal.threadLocal.get(); MyThreadLocal.threadLocal.set(str + "a"); System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get()); } try { Thread.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } } } } <strong>} </strong>
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值! 线程IntegerTask1: 0 调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值! 线程IntegerTask2: 0 调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值! 调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值! 线程StringTask1: a 线程StringTask2: a 线程StringTask1: aa 线程StringTask2: aa 线程IntegerTask1: 1 线程IntegerTask2: 1 线程StringTask1: aaa 线程StringTask2: aaa 线程IntegerTask2: 2 线程IntegerTask1: 2 线程StringTask2: aaaa 线程StringTask1: aaaa 线程IntegerTask2: 3 线程IntegerTask1: 3 线程StringTask1: aaaaa 线程StringTask2: aaaaa 调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值! 线程IntegerTask2: 0 调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值! 线程IntegerTask1: 0
可以看到,虽然只有一个类变量threadlocal,但对各个线程来说,好像是它们自己的局部变量一样,互相不影响。
threadLocal原理
在介绍ThreadLocal的原理之前,先要介绍几个类
首先,这个threadLocal是和各个线程相关的,所以可以想象,这个ThreadLocal的原理肯定和Thread类有关系。
在Thread类中,有个类变量:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
这个类变量很容易理解的,是这个Thread对象所代表的线程的ThreadLocalMap。这是个Map,里面的每个entry的key是ThreadLocal实例对象。
而这个ThreadLocalMap类呢,是ThreadLocal类的一个嵌套类(就static内部类)。
从set方法的源码来看原理
先说说大概思路吧,当为一个ThreadLocal的实例变量,set的时候,首先会获得当前线程(因为是它自己去获得当前线程,所以这个线程是它自己本身),然后拿到这个线程的ThreadLocalMap。然后再以这个要set的ThreadLocal实例为key,set的东西为value放进到这个ThreadLocalMap里面去。
来看具体源码
set方法:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread();//获得当前线程,也就它自己 ThreadLocalMap map = getMap(t);//获得这个线程的ThreadLocalMap,下面贴了这个方法的代码 if (map != null) map.set(this, value); else createMap(t, value); } /** * 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; }
我们看到,如果这个threadLocalMap存在,就交给这个map去执行set方法。
既然是map的set,我们要关心的肯定是在map中table中的桶序号是怎么生成的:
Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1);
和hashMap一样,用key得到的一个hash值然后与上长度减一。
所以关键看这个threadLocalHashCode,要看懂这个,还要介绍下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); }
首先,对于每个ThreadLocal对象,都有一个final的threadLocalHashCode,这是不能变的。
对于每一个ThreadLocal对象,都有一个final修饰的int型的threadLocalHashCode不可变属性,对于基本数据类型,可以认为它在初始化后就不可以进行修改,所以可以唯一确定一个ThreadLocal对象。
但是如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性:
在ThreadLocal类中,还包含了一个static修饰的AtomicInteger([əˈtɒmɪk]提供原子操作的Integer类)成员变量(即类变量)和一个static final修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性
set的分析就大概到这,理解了主要原理就好,就不深入了。
最后再捋一下这几个类的关系和原理(有点乱):
ThreadLocal类里面有个static内部类——ThreadLoalMap,这个map中的key是ThreadLocal的实例自己,value是set的object。
Thread类里面有个类变量——ThreadLocalMap threadLocals。
所以调用threadLocal的set方法的时候,从当前线程中拿到它的Thread实例,然后从中拿threadLocalMap,然后再根据这个threadLocal实例生成一个hash值定位到那个entry巴拉巴拉。
关于ThreadLoca的内存泄露问题
先来看ThreadLocalMap这个Map中定义的Entry,也就是存在map中的对象。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
这个Entry是继承弱引用的!!
Object是直接用强引用来指,然后key是用weak来指。
所以下面来看使用一个ThreadLocal的时候的引用、对象关系图:
(图来自https://www.cnblogs.com/xzwblog/p/7227509.html)
如上图,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄露。
ThreadLocalMap在设计的时候也想到这个问题,于是想出了一些对策:
在调用ThreadLocal的get和set方法的时候,会帮你删掉key为null的那些entry,删掉后,entry中的value也没有了强引用,自然会被gc掉。
但光是这样是不足够的,所以保险起见,在使用ThreadLocal的时候最好:
1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
使用场景
ThreadLocal的主要用途是为了保持线程自身对象和避免参数传递,主要适用场景是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。
例子:
数据库连接,最好每个线程有自己的数据库连接。——https://www.2cto.com/kf/201805/750397.html这个例子挺简单易懂的。
session相关的。
参考文章
https://www.cnblogs.com/xzwblog/p/7227509.html——《彻底理解ThreadLocal》这个原理讲得清楚很多。
https://www.cnblogs.com/coshaho/p/5127135.html——《ThreadLocal用法详解和原理 》这个例子把ThreadLocal的用法讲的很清晰。
https://www.2cto.com/kf/201805/750397.html——《什么是ThreadLocal?ThreadLocal应用场景在哪?》一个数据库连接的使用场景的例子。