在网上看了很多关于ThreadLocal的介绍,晕晕乎乎,终于算是搞清了Thread, ThreadLocalMap 和ThreadLocal三者的关系,赶紧记录以下自己的理解,以防忘记。
一、什么是ThreadLocal
我们写的Java代码可能会被多个线程并发执行,尤其是在Spring应用中。那么,我们在代码中写的变量,就是多个线程共享的,因此可能会造成一系列的线程安全问题,也因此出现了很多的解决方案,如加锁等,当然,这个不是这篇随笔的主题。
ThreadLocal就是为了存储一些线程私有的变量,从名字就能看出来。也就是说,这些变量不能被其他线程随便访问,只属于当前线程(可以共享给其子线程,后面会叙述)。至于应用场景,网上可以找到很多,就不记录了,目前我还没直接用过ThreadLocal。
二、ThreadLocal为什么是线程私有
首先要看一下ThreadLocal相关的类
有三个类: ThreadLocal,ThreadLocalMap,当然,还有Thread。
首先先说一下 ThreadLocal和ThreadLocalMap的关系
从代码结构来说,ThreadLocalMap是ThreadLocal的静态内部类:
public class ThreadLocal { 【略】...... 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; } }
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
【略】...... }
【略】......
}
那么ThreadLocalMap又是啥?从名字看,似乎是存储ThreadLocal的Map,其实它就是。那么为什么它要写成ThreadLocal的内部类呢?总感觉有点怪。后面再说。
可以看到,ThreadLocalMap里面定义了Entry类,Entry里又有key又有value的,同时ThreadLocalMap里面还有一个Entry数组,是不是有HashMap内味儿了?那么,ThreadLocal就是这么个东西:
这里的threadlocal1 和threadlocal2 都是ThreadLocal的实例,也就是说,ThreadLocal只是作为key存在的,我们在一个线程里可以new很多个ThreadLocal,这样就可以存入很多个value。
那么,他们和Thread类是什么关系?
Thread类是Java中用来标志一个线程的类,里面有很多native方法来控制一个线程。从源码可以看出,Thread中是包含两个ThreadLocalMap变量的:
public class Thread implements Runnable { 【略】... /* 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; 【略】... }
分别是 threadLocals 和 inheritableThreadLocals,什么区别后面再说。
这下他们三个的关系就建立了,大概可以这么表示:
从结构上来看,ThreadLocal似乎定义在Thread类里面更合适一些。那么为什么没有这么做呢?好像很少有人问到这个问题,我从*上看到一个回答,大致意思是说,ThreadLocal这个东西,并不是每个线程都会用得到的,如果把ThreadLocalMap写在Thread里面,每次新建线程都会被加载,那么开销将会变大。但是我个人感觉,可以为ThreadLocalMap单独写一个类,写成ThreadLocal的内部类总感觉。。。有点怪(*老哥说是个人风格)。
三、写代码试试呗
1、ThreadLocal
从结果可以看出来,子线程是不能读到主线程的ThreadLocal数据的,而且在子线程里面set的数据,在主线程也读不到
public static void main(String[] args) throws InterruptedException { ThreadLocal<String> key1=new ThreadLocal<>(); ThreadLocal<String> key2=new ThreadLocal<>(); key1.set("val1"); System.out.println("main thread "+key1.get()); new Thread(new Runnable() { @Override public void run() { System.out.println("thread1 "+key1.get()); key2.set("val2"); System.out.println("thread1 "+key2.get()); } }).start(); Thread.sleep(1000); // 主线程休眠,让子线程先执行 System.out.println("main thread "+key1.get()); System.out.println("main thread "+key2.get()); }
输出结果:
main thread val1
thread1 null
thread1 val2
main thread val1
main thread null
2、InheritableThreadLocal
把ThreadLocal换成InheriableThreadLocal。
结果有了一些变化,子线程可以读到主线程写入的val1了,然而子线程写入的val2在主线程还是不能读到。 也就是说,主线程是可以通过InheriableThreadLocal向子线程传数据的。
public static void main(String[] args) throws InterruptedException { ThreadLocal<String> key1=new InheritableThreadLocal<>(); // 这里变啦 ThreadLocal<String> key2=new InheritableThreadLocal<>(); key1.set("val1"); System.out.println("main thread "+key1.get()); new Thread(new Runnable() { @Override public void run() { System.out.println("thread1 "+key1.get()); key2.set("val2"); System.out.println("thread1 "+key2.get()); } }).start(); Thread.sleep(1000); // 主线程休眠,让子线程先执行 System.out.println("main thread "+key1.get()); System.out.println("main thread "+key2.get()); }
输出结果:
main thread val1
thread1 val1
thread1 val2
main thread val1
main thread null
四、关于弱引用和内存泄漏
外卖到了,下次一定。