苏格团队
作者: ZhiCheng
与 Synchonized 的对照:
ThreadLocal 和 Synchonized 都用于解决多线程并发訪问. 可是 ThreadLocal 与 synchronized 有本质的差别. synchronized 是利用锁的机制, 使变量或代码块在某一时该仅仅能被一个线程訪问. 而 ThreadLocal 为每个线程都提供了变量的副本, 使得每个线程在某一时间訪问到的并非同一个对象, 这样就隔离了多个线程对数据的数据共享. 而 Synchronized 却正好相反, 它用于在多个线程间通信时可以获得数据共享.
Synchronized 用于线程间的数据共享, 而 ThreadLocal 则用于线程间的数据隔离.
一, 用法
把要线程隔离的数据放进 ThreadLoacl
- static ThreadLocal<T> threadLocal = new ThreadLocal<T>() {
- protected T initialValue() {
这里一般 new 一个对象返回
}
}
线程获取相关数据的时候只要
threadLocal.get()
想修改, 赋值只要
threadLocal.set(val)
二, 原理分析
1,get() 方法
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {// 当 map 已存在
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();// 初始化值
- }
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
上面先取到当前线程, 然后调用 getMap 方法获取对应的 ThreadLocalMap,ThreadLocalMap 是 ThreadLocal 的静态内部类, 然后 Thread 类中有一个这样类型成员, 所以 getMap 是直接返回 Thread 的成员
ThreadLocal.ThreadLocalMap threadLocals = null;
来看下 ThreadLocal 的内部类 ThreadLocalMap 源码, 留个大致印象
- static class ThreadLocalMap {
- private static final int INITIAL_CAPACITY = 16;// 初始数组大小
- private Entry[] table;// 每个可以拥有多个 ThreadLocal
- private int size = 0;
- private int threshold;// 扩容阀值
- static class Entry extends WeakReference<ThreadLocal<?>> {
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
- 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);
- }
- private void set(ThreadLocal<?> key, Object value) {
- 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) {
- // 循环利用 key 过期的 Entry
- replaceStaleEntry(key, value, i);
- return;
- }
- }
- tab[i] = new Entry(key, value);
- int sz = ++size;
- if (!cleanSomeSlots(i, sz) && sz>= threshold)
- rehash();
- }
- ...
- }
可以看到有个 Entry 内部静态类, 它继承了 WeakReference, 总之它记录了两个信息, 一个是 ThreadLocal<?> 类型, 一个是 Object 类型的值. getEntry 方法则是获取某个 ThreadLocal 对应的值, set 方法就是更新或赋值相应的 ThreadLocal 对应的值. 里面涉及到扩容策略, Entry 哈希冲突, 循环利用等等不再深入, 留个大致印象就好.
回顾下 get() 方法中的代码
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();
map 为 null 或 e 为 null 就会走到 setInitialValue, 如果我们是第一次 get() 方法, 那 map 会是空的, 所以接下来先看 setInitialValue() 方法
- private T setInitialValue() {
- // 调用我们实现的方法得到需要线程隔离的值
- T value = initialValue();
- Thread t = Thread.currentThread();
- // 拿到相应线程的 ThreadLocalMap 成员变量
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- return value;
- }
上面 initialValue 就是实例化 ThreadLocal 要实现的方法, 这里又取了线程的 ThreadLocalMap, 不为空就把值 set 进去 (键为 TreadLocal 本身, 值就是 initialValue 返回的值); 为空就创建一个 map 同时添加一个值进去, 最后返回 value.
map.set(this, value) 这句代码在上面的 ThreadLocalMap 源码中可以看到大致流程, 下面看看 createMap() 做了什么事
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
- ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
- table = new Entry[INITIAL_CAPACITY];
- int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
- // 创建一个 Entry, 加入数组
- table[i] = new Entry(firstKey, firstValue);
- size = 1;
- setThreshold(INITIAL_CAPACITY);
- }
可以看到在 new ThreadLocalMap 之后, 就会创建一个 Entry 加入到数组中, 最后把 ThreadLocalMap 的引用赋值给 Thread 的 threadLocals 成员变量
在回顾下 get() 方法中的代码
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();
现在 map 不会为空了, 再次调用 get 方法就会调用 map 的 getEntry 方法 (上面的 ThreadLocalMap 源码中可以看到大致流程), 拿到相应的 Entry, 然后就可以拿到相应的值返回出去
2,set 方法
分析完 get() 方法, 那么 set() 方法就自然而然的明白了, 就不再累述
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
总结
ThreadLocal 的实现线程隔离的原理是, 在每个线程中维护一个 Map, 键是 ThreadLocal 类型, 值是 Object 类型. 当想获取 ThreadLocal 的值时, 就从当前线程中拿出 Map, 然后在把 ThreadLocal 本身作为键从 Map 中拿出值.
来源: https://juejin.im/post/5c948d3b6fb9a070ba314002