简介
说明
本文介绍Java的ThreadLocal的原理。
ThreadLocal是Java多线程常用的技术,也是Java后端面试中常见的问题。
ThreadLocal简介
ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
ThreadLocal用法
要明白原理,先要知道用法。用法详见:Java多线程系列–ThreadLocal的用法(有实例) – 自学精灵
本文将最简用法贴出:
package com.example.a; public class Demo { static ThreadLocal<String> threadLocal = new ThreadLocal<>(); static void print(String str){ System.out.println(str + ":" + threadLocal.get()); } public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { @Override public void run() { threadLocal.set("abc"); print("thread1 variable"); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { threadLocal.set("def"); print("thread2 variable"); } }); thread1.start(); thread2.start(); } }
运行结果
thread1 variable:abc thread2 variable:def
流程追踪
追踪上边简单用法的流程
set流程
threadLocal.set("abc"); // ThreadLocal#set //传进来的参数名为value //获取当前线程 Thread t = Thread.currentThread(); //获得此线程的ThreadLocalMap变量。(若为null则创建它,若有则直接使用) // ThreadLocalMap是ThreadLocal的静态内部类。即:ThreadLocal.ThreadLocalMap // ThreadLocalMap内部是弱引用(WeakReference) ThreadLocalMap map = getMap(t); // ThreadLocal#getMap return t.threadLocals; //保存数据。key为当前ThreadLocal对象,value为传进来的值 map.set(this, value)
get流程
threadLocal.get() // ThreadLocal#get //获取当前线程 Thread t = Thread.currentThread(); //获得此线程的ThreadLocalMap变量 ThreadLocalMap map = getMap(t); //获得当前线程的以当前ThreadLocal为key的值 ThreadLocalMap.Entry e = map.getEntry(this); //强制转换为原来的类型并返回 T result = (T)e.value; return result;
源码追踪
set源码
追踪java.lang.ThreadLocal#getMap
它返回的是t的threadLocals变量,追踪它
可以看到,每个线程(Thread)内部都持有一个ThreadLocal.ThreadLocalMap实例
追踪map.set(this, value)
也就是:java.lang.ThreadLocal.ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
Entry[] tab = table;
tab[i] = new Entry(key, value);
注意上边这两行代码,可以看到,数据最终是存到了Entry[] table这个数组里边。
追踪Entry,它的源码如下:
public class ThreadLocal<T> { // 其他代码 static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } } }
Entry继承自WeakReference,这个与内存泄露有关。后边会专门写一篇这个博客。
key为ThreadLocal对象,value为传进来的对象。
get源码
追踪ThreadLocalMap.Entry e = map.getEntry(this)
java.lang.ThreadLocal.ThreadLocalMap#getEntry
private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }
可以看到,它以ThreadLocal对象为key去java.lang.ThreadLocal.ThreadLocalMap#table去查找value。
原理总结(图文)
每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key是 ThreadLocal 实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。图中的虚线表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key的,弱引用的对象在 GC 时会被回收。
请先
!