ThreadLocal理解
ThreadLocal理解
因为这几天在练习写 reids 锁,涉及使用ThreadLocal 。虽然以前也写过关于ThreadLocal 的学习文档。但是当时学的时候在意的是速度,并没有仔细熟悉和掌握,所以欠债是要还的。
jdk版本:1.8(都已经出了好高的版本,我还在啃1.8) 代码入手
先看下面的代码 package com.jmmq.load.jim.test; public class ThreadLocalTest { private static final ThreadLocal tl = new ThreadLocal(); public static void main(String[] args) { Thread thread = Thread.currentThread(); String name = "jimmy"; tl.set(name); System.out.println(tl.get()); } } --- jimmy
给人直观的感觉 ThreadLcal 就是一个可以存储数据的容器。完全体现不出来ThreadLocal 的用法,那我们对程序进行改进下package com.jmmq.load.jim.test; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadLocalTest { private static final ThreadLocal tl = new ThreadLocal(); public static void main(String[] args) { String name = "jimmy - "; tl.set(name + Thread.currentThread().getName()); new Thread(() -> { tl.set("giao giao ~" + Thread.currentThread().getName()); System.out.println(tl.get()); } ).start(); // 实际开发建议自己写线程池 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2); for (int i = 0; i < 3; i++) { final int index = i + 1; fixedThreadPool.execute(new Runnable() { @Override public void run() { try { tl.set("盖亚 v " + index + " v " + Thread.currentThread().getName()); // 让线程不同时执行完,以便观察tl内容变化 Thread.sleep(1000 * (5 - index)); System.out.println(tl.get()); } catch (Exception e) { // 实际开发禁止使用 e.printStackTrace(); e.printStackTrace(); } } }); } fixedThreadPool.shutdown(); System.out.println(tl.get()); } } -------------- giao giao ~Thread-0 jimmy - main 盖亚 v 2 v pool-1-thread-2 盖亚 v 1 v pool-1-thread-1 盖亚 v 3 v pool-1-thread-2
这里可以看到不同线程从 ThreadLocal 中取数据的时候取出来的值都是线程自己塞进去的值。源码分析
一种方式是根据程序进行 debug 看代码如何运行的。
我这里因为之前有看过所以直接给出部分关键地方进行分析。
我们从 ThreadLocal.set() 入手看看,这里面到底干了啥。public void set(T value) { // 1、拿到当前线程 Thread t = Thread.currentThread(); // 2、获取 当前线程中的 threadLocals ThreadLocalMap map = getMap(t); if (map != null) //3-1 将当前线程多为key放在threadLocals,传入的值作为value map.set(this, value); else // 3-2 若当前线程中的 threadLocals为null则创建 createMap(t, value); }
这里要注意 2 步骤。看下这方法是什么东东ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
这个 threadLocals 是什么,继续读Thread类的源码找到属性threadLocals /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
这样基本就看完了 ThreadLocal.set() 方法了。
总结起来 就是在 set 的时候是将value 塞进了线程自己独有的属性threadLocals 中,使用当前线程作为key 。
沿着这个思路来看 ThreadLocal.get() 取数据的时候就是根据当前线程作为key 来查找threadLocals 里面的值。看下源码是否是这么实现的。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(); }
一目了然。
一个常见的面试题:
那在看一眼 ThreadLocal.ThreadLocalMap 是个啥玩意。这里面源码挺多的就不复制过来了。原理和Map有些类似。相信大家一定听过这样一道面试题:为什么 ThreadLocal 可能会导致内存泄露?
一些没有看过源码的童鞋看网上答案肯定也知道,就是 ThreadLocal 里面采用的是弱引用,所以有可能会出现内存泄漏。
其实这个若引用时在 ThreadLocalMap 中r如下。至于为什么弱引用可能引起内存泄漏,这个以后在讨论。static class ThreadLocalMap { ... static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... }