ThreadLocal使用与原理探索
ThreadLocal原理探索
ThreadLocal与synchronized的区别是:
ThreadLocal:每个线程单独有一个副本
synchronized:共用同一个资源,加锁
显然ThreadLocal是用空间换时间
ThreadLocal大致存储是这样的:
- 每个线程有一个ThreadLocalMap,即使不同的ThreadLocal实例,只要是同一个线程他们共享的都是同一个ThreadLocalMap
- ThreadLocalMap以ThreadLocal实例为key,且设置key为弱引用进行存储
- 设置key为弱引用的好处是,我不用这个ThreadLocalMap的时候,我只需要显示将变量赋值为null,因为它没有强引用了,我还设置它是弱引用,那GC就直接回收了,这样就不会造成内存泄漏的问题。【当然如果线程迟迟不结束,如果这个ThreadLocal对象还被被回收了,那么Map中key虽然没有了,但是value还在,而这个Map的生命周期和线程生命周期一致,因此我们当前线程不使用ThreadLocal了,就调用其remove放法,进行释放,否则仍然会造成内存泄漏】
- 当然如果我们只是当前线程不需要使用ThreadLocal了,那么我们在当前线程直接手动调用remove()方法即可
接下来我试着剖析一波
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
可以看出,首先获取当前线程的ThreadLocalMap,如果没有创建一波,它是以当前ThreadLocal实例对象为key进行存储
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
可以看出,首先获取当前线程的ThreadLocalMap,如果有则获取当前首先获取当前线程的ThreadLocal实例对象的Entry,然后取出值,否则调用方法和set源代码一样,只不过设置默认值null
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
可以看出,首先获取当前线程的ThreadLocalMap,然后移除当前ThreadLocal实例对象为key的value
整个测试
里面许多的注释是从最初探讨的时候写的,有的不一定对,以我上访分析源码为准
/** * @author ningxinjie * @date 2021/2/3 */ // https://blog.csdn.net/weixin_42388901/article/details/96491793 public class ThreadLocalTest { // 测试下来可以看到,threadLocal果然是内部按照线程为key,每个线程有各自的ThreadLocalMap,本线程直接取出来的就是本线程的ThreadLocalMap; // 其中remove针对的也是本线程的,因此本线程不使用的时候最好手动调用一下,因为ThreadLocalMap的key是弱引用 // 每个ThreadLocal实例,内部为每个线程创建一个ThreadLocalMap,这个Map以当前ThreadLocal实例为key,且内部存放key是弱引用 // 为什么设置成弱引用呢?这样我们不用这个对象的时候直接将这个对象=null即可,如果设置成强引用,那么内部的map的key是这个对象。这个对象一直是key存放在map中 // ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收, // 这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链: static ThreadLocal threadLocal = new ThreadLocal(); // 目前我们不把它设置为null,他就是强引用,因此目前不会被回收 static ThreadLocal threadLocal2 = new ThreadLocal(); // 都讲jdk建议设置成private static这样强引用就不会被回收,我们不用手动置null,这样没有了强引用就变成了弱引用,gc即回收 public static void main(String[] args) throws Exception { threadLocal.set("this is main"); new Thread(new Runnable() { @Override public void run() { threadLocal.set("this is thead01"); System.out.println("thread01 set ok"); System.out.println(threadLocal.get()); } },"thead01").start(); Thread.sleep(200); // 同一个线程共用同一个ThreadLocalMap,但是key是ThreadLocal实例 ,这里的实例是threadLocal2 ,因此没有值 System.out.println(threadLocal2.get()); Object o = threadLocal.get(); System.out.println(o); // 移除当前线程的ThreadLocalMap threadLocal.remove(); System.in.read(); }