首先ThreadLocal
类是一个线程数据绑定类, 有点相似于HashMap<Thread, 你的数据>
(但实际上并不是如此), 它全部线程共享, 但读取其中数据时又只能是获取线程本身的数据, 写入也只能给线程本身的数据java
public class ThreadLocalDemo { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { threadLocal.set("zhazha" + Thread.currentThread().getName()); String s = threadLocal.get(); System.out.println("threadName = " + Thread.currentThread().getName() + " [ threadLocal = " + threadLocal + "\t data = " + s + " ]"); }, "threadName" + i).start(); } } }
从他的输入来看, ThreadLocal
是同一个, 数据存的是线程本身的名字, 因此和threadName
是同样的名称shell
threadName = threadName9 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName9 ] threadName = threadName3 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName3 ] threadName = threadName7 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName7 ] threadName = threadName0 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName0 ] threadName = threadName6 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName6 ] threadName = threadName1 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName1 ] threadName = threadName2 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName2 ] threadName = threadName4 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName4 ] threadName = threadName5 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName5 ] threadName = threadName8 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName8 ]
咱们使用获取到一个保存数据库请求, tomcat会有一个线程去操做数据库保存数据和响应数据给客户, 而操做数据库须要存在一个数据库连接Connection
对象, 只要是同一个数据库连接, 就能够获得同一个事务
但一个线程是如何获取同一个Connection
从而获取同一个事务 ?
方法其实很简单, 使用 ThreadLocal
绑定在线程中, 相似于Map<Thread, Connection>
去存储数据库
get
方法分析数组
public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 获取ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); // map不为null if (map != null) { // 根据this获取咱们的entry ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 若是map获取为空, 则初始化 return setInitialValue(); }
根据上面源码分析发现ThreadLocal
底层使用的不是相似Map<Thread, Data>
这种结构而是
每一个线程都有一个属于本身的ThreadLocalMap
结构
而他的结构是这样的缓存
其中的table
数组在上面的 setInitialValue()
方法建立详细源码在这tomcat
private T setInitialValue() { // 这个方法在咱们的用例中没写, 因此默认放回 null T value = initialValue(); Thread t = Thread.currentThread(); // 获取线程单独的 ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { // 若是咱们初始化了initialValue() 方法, 那么它默认初始化的值会被设置到这里, // 可是实际上咱们用例为null, 因此不会执行这段代码 map.set(this, value); } else { // 线程ThreadLocalMap 没被建立, 须要建立出来, // 其中的 table 数组在这里被建立 createMap(t, value); } // 这里我没分析, 忽略了 if (this instanceof TerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this); } return value; }
他会在ThreadLocalMap
中调用构造方法初始化网络
// 其中 firstValue是咱们的值 void createMap(Thread t, T firstValue) { // 关注下 this , 它是ThreadLocal t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 咱们的table在这里被建立, INITIAL_CAPACITY == 16 table = new Entry[INITIAL_CAPACITY]; // 获取不超过16的hashCode int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 根据计算出来的HashCode设置到对应的table数组中, 这里key是ThreadLocal, value是咱们的值 table[i] = new Entry(firstKey, firstValue); // 初始时, 已经有一个值了, 因此size = 1 size = 1; // 设置扩容阈值加载因子 threshold = len * 2 / 3; 默认为长度的三分之二 setThreshold(INITIAL_CAPACITY); }
从这段代码能够发现, firstKey
实际上是咱们ThreadLocalMap
中的key
, 而firstKey
就是咱们的ThreadLocal
, 而value
就是咱们 initialValue()
方法返回的值, 这里默认为null
, 因此咱们能够得出这样一幅图jvm
总结下
每一个线程都有一个属于本身的ThreadLocalMap
类, 他用于关联多个以ThreadLocal
对象为key
, 以你的数据
为value
的Entry
对象, 且该对象的key
是一个弱引用对象
接下来咱们分析下这个类Entry
, 它继承了弱引用类WeakReference
ide
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { // ThreadLocal被设置为弱引用 super(k); // 保存value value = v; } }
发现 ThreadLocal
被设置为弱引用源码分析
为何前面的Entry
须要继承弱引用类WeakReference
呢?
首先了解下什么是引用
Object obj = new Object()
SoftReference
类实现软引用, 通常用于相对比较重要但又能够不用的对象, 好比: 缓存ReferenceQueue
)中或者对象中, 好比: ThreadLocalMap
的Entry
对象, 须要依附于ThreadLocal
才可以不被删除掉NIO
上, 用于引用直接内存, java提供类PhantomReference
来实现虚引用Entry
对象须要为弱引用?答案很明显, 防止内存泄漏[1], 咱们来详细分析分析
首先, 咱们知道ThreadLocalMap
中存放的是一个一个Entry
对象, 而 Entry
对象中的key
(ThreadLocal
)被设计成弱引用若是key
被设置成null
(好比: 外部的测试用例中的private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
这个对象被设置为 threadLocal = null
) 则, 你会发现此时Entry
的对象key = null value = xxxx
(此时这个Entry
实质上是没有用的, 连key
都给设置成null
, 它的value
还有什么用?) 而ThreadLocalMap
中存储的仍是Entry
对象的地址, 此Entry
不会被回收, 但Entry
对象的key
被设置成弱引用, 就不同了, 直接会被回收掉它
[1]内存泄漏: 程序中己动态分配的堆内存因为某种缘由程序未释放或没法释放,形成系统内存的浪费,致使程序运行速度减慢甚至系统崩溃等严重后果
再次强调, 下面这段话别信, 仔细看到最后, 你会发现这被打脸了
其实应该是没什么问题了(__被本身打脸了, 别信这句话__), 只不过不少网友以为Entry
中的key
虽然是弱引用, 但Entry
可能不会被回收, 由于entry
的value
是强引用, 可能致使线程下的entry
没法被回收掉, 最好推荐使用threadLocal.remove
方法删除掉, 前面说的threadLocal = null
方法不推荐使用, 那么为了以防万一吧, 仍是手动调用下remove
方法比较好一点
下面是我对threadLocal = null
方式的代码测试:
public class ThreadLocalDemo { private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>() { @Override protected String initialValue() { return "1"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>() { @Override protected String initialValue() { return "2"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException { // 获取ThreadLocalMap Thread thread = Thread.currentThread(); Class<? extends Thread> clazz = thread.getClass(); Field threadLocals = clazz.getDeclaredField("threadLocals"); threadLocals.setAccessible(true); Object threadLocalsObj = threadLocals.get(thread); // 获取ThreadLocalMap下的table数组 Class<?> threadLocalsMapClass = threadLocalsObj.getClass(); Field tableField = threadLocalsMapClass.getDeclaredField("table"); tableField.setAccessible(true); Object[] tableObj = (Object[]) tableField.get(threadLocalsObj); threadLocal1.set("zhazha"); threadLocal2.set("xixi"); System.out.println(threadLocal1.get()); System.out.println(threadLocal2.get()); // 在这里下一个断点看看ThreadLocal被回收, Entry是否被回收 threadLocal1 = null; threadLocal2 = null; System.gc(); Thread.sleep(5000); System.out.println(tableObj); System.out.println("主线程结束"); } }
输出是这样的:
WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by com.zhazha.threadlocal.ThreadLocalDemo (file:/D:/program/codes/java/Concurrentcy/reviewjuc/target/classes/) to field java.lang.Thread.threadLocals WARNING: Please consider reporting this to the maintainers of com.zhazha.threadlocal.ThreadLocalDemo WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release zhazha xixi [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;@aecb35a 主线程结束
若是上面的代码不调用gc
方法, 很长一段时间内不会被回收, 应该是jvm gc
还没开始被动回收
但!!!但!!!但!!! 看调试代码
数组中的referent
字段仍是存在的, 下图是gc
回收以前查看数组中的元素发现, 字段referent
(也就是ThreadLocal
) 它还在
在gc
方法执行完毕后, referent
被回收掉了, referent = null
了
可是那个对象怎么回事??? 没被回收掉?? 打脸了??? 求助广大网友给我看看
那让咱们试试 remove
方法试试?
好了, 直接没了, 找不到那两个属性了
这An illegal reflective access operation has occurred
这个问题怎么帮? 这回真不知道了, 应该不影响咱们的代码么?
算了为了把这个红色的字改没掉, 改了改源码
public class ThreadLocalDemo { private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>() { @Override protected String initialValue() { return "1"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>() { @Override protected String initialValue() { return "2"; } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("threadLocal1被回收"); } }; private static Unsafe unsafe; static { Class<Unsafe> unsafeClass = Unsafe.class; Unsafe unsafe = null; try { Field unsafeField = unsafeClass.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); ThreadLocalDemo.unsafe = (Unsafe) unsafeField.get(null); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException, NoSuchFieldException { Thread thread = Thread.currentThread(); long threadLocalsFieldOffset = unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals")); Object threadLocalMapObj = unsafe.getObject(thread, threadLocalsFieldOffset); long tableOffset = unsafe.objectFieldOffset(threadLocalMapObj.getClass().getDeclaredField("table")); Object tableObj = unsafe.getObject(threadLocalMapObj, tableOffset); threadLocal1.set("zhazha"); threadLocal2.set("xixi"); System.out.println(threadLocal1.get()); System.out.println(threadLocal2.get()); threadLocal1 = null; threadLocal2 = null; // threadLocal1.remove(); // threadLocal2.remove(); System.gc(); System.out.println(tableObj); System.out.println("主线程结束"); } }
好了没这个问题了
zhazha xixi threadLocal1被回收 threadLocal1被回收 [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;@7dc222ae 主线程结束 与目标VM断开链接, 地址为: ''127.0.0.1:58958',传输: '套接字'', 传输: '{1}' 进程已结束,退出代码0