谈一谈不常见却又不可少的ThreadLocal
在写ThreadLocal以前,须要先巩固下一点相关知识:Java内存模型及共享变量的可见性。java
内存模型中全部变量存储在主内存中,当一个线程中要使用某个变量时,须要从主内存复制该变量到其线程内才能操做,此时线程中操做的是主内存变量的副本,操做完成后再刷回主内存。刷回的实质就是变量赋值数据结构
若是多个线程访问同一个变量时,每一个线程都具备一个副本,操做完毕后都会刷回主内存,刷回时间存在前后,则赋值有前后,固然后者会覆盖前者,这是形成可见性问题的次要缘由。dom
引入以上知识点后,再来讲明ThreadLocal。一个线程想使用某个变量,因而从主内存复制该共享变量到线程内部中。使用完毕后想再下次再次使用该变量时,获得的变量副本是上次使用的副本,而不是从主内存的变量再次复制过来的副本,而且不想让其余线程影响到该变量。这就是ThreadLocal的目的,其实现不是经过共享变量这种方式实现的,详细内容下面介绍ide
目的很明确,可是身处JAVA内存模型中要遵循内存模型规范,下面看看JDK是如何即知足内存模型规范,又知足ThreadLocal目的。函数
知足内存模型
这点很简单,就是你该怎么样还怎么样,仍然受你管辖,该复制就复制,该刷回就刷回,不可见仍是会形成不可见。工具
知足ThreadLocal目的
多个线程都能访问的变量才叫共享变量,若是控制变量的访问方式,使其余线程线程不能访问就能够了。控制方式就是将线程与变量的一一对应,将该变量的访问入口控制到只有该线程便可,JDK中的作法就是让线程持有这个变量(绑定到线程自己)优化
线程能够绑定变量,可是并不知道须要绑定多少个,因而将这个存储功能仍是交给专门的数据结构—>Map。而且还专门设计了一个用来访问这个Map的工具,这个工具就是ThreadLocal。而且这个Map的key为ThreadLocal实例的引用地址,value存储真正的变量。this
这样设计就到达目的了。ThreadLocal构建时接收个泛型告诉你存储的变量是一个对象类型。spa
ThreadLocal设计
核心Map设计
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... }
这里Map的Entry被设计为弱引用。线程
内存存储图示以下
弱引用WeakReference
WeakReference是Java语言规范中为了区别直接的对象引用(程序中经过构造函数声明出来的对象引用)而定义的另一种引用关系。WeakReference标志性的特色是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象以外全部的对象解除引用后,该对象即可以被GC回收),只不过在被对象回收以后,reference实例想得到被应用的对象时程序会返回null。
这里使用弱引用的缘由:ThreadLocal目的就是达到变量只能本身访问别的线程不能访问的目的,Map设计的key为ThreadLocal实例的引用地址,value为变量,当线程存在而ThreadLocal实例被回收时,Map中的value仍是存在的(缘由后续说明)导致该变量的可达性分析失败,从而致使此阶段的内存泄露。当设计为弱引用后,若是key被销毁了(强引用销毁),那么value就处于可被回收状态,从而避免内存泄露。
临时内存泄露图示
Map的持有状态
在Thread类定义中这样定义ThreadLocalMap
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }
也就是ThreadLocalMap是线程类Thread持有的对象,一个线程持有一个ThreadLocalMap,只有线程存在,则ThreadLocalMap必存在。这也是ThreadLocal对象销毁后,value还存在的缘由,ThreadLocalMap还被Thread强引用。只有线程销毁时,ThreadLocalMap才会随之销毁。
ThreadLocal的使用
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); // 接收泛型
threadLocal.set(1); // 对应泛型
Integer var=threadLocal.get();
ThreadLocal底层原理
设置变量 Set
public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 从当前线程中获取一个ThreadLocalMap实例 ThreadLocalMap map = getMap(t); // map存在则放入key,value key为当前ThreadLocal对象引用 if (map != null) map.set(this, value); // map不存在则构建,一样放入key,value else createMap(t, value); }
ThreadLocalMap实例的获取就是从Thread中获取的,也就是上面的持有状态,拿到以后就能够向Map结构中存储key,value了,前面也说过了,这里key存在的ThreadLocal实例的引用地址,value存在的变量的引用地址。一个线程能够存储多个ThreadLocal实例。
getMap(Thread t)
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
就是获取当前线程持有的ThreadLocalMap
createMap(Thread t, T firstValue)
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
给当前线程初始化ThreadLocalMap实例,并set初始值。key为ThreadLocal实例的引用地址
获取变量Get
public T get() { Thread t = Thread.currentThread(); //① 获取当前线程t ThreadLocalMap map = getMap(t); //②从当前线程中获取一个ThreadLocalMap if (map != null) { //③ ThreadLocalMap不为null则从map中获取value ; ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();//④ThreadLocalMap为null则调用setInitialValue() 返回—初始化的值 }
在最后一个return以前的代码就是从当前线程的ThreadLocalMap中经过Map的get方法获取变量引用的过程。这是Map的基本用用法。
存在如下状况则须要获取初始的默认值,这是一个对外开放的功能,就是能够指定ThreadLocal对应的变量的初始默认值,默认为null,能够被重写
- ThreadLocalMap未实例化
- ThreadLocalMap已实例化,可是尚未Set变量
设置默认的初始值
protected T initialValue() { return null; } private T setInitialValue() { T value = initialValue(); //① 初始化为null Thread t = Thread.currentThread(); //② 获取当前线程 ThreadLocalMap map = getMap(t); //③ 从当前线程中获取一个ThreadLocalMap if (map != null) map.set(this, value); //④ ThreadLocalMap不为null,则存储key=this,value=value else createMap(t, value);//⑤ ThreadLocalMap为null,则为当前线程建立一个ThreadLocalMap并初始化值 return value; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
代码流程也很简单,只是为了功能优化了,进行的统一封装
- ThreadLocalMap的惰性实例化
- 获取ThreadLocalMap时须要Thread,而ThreadLocal只是访问控制工具,因而须要打通Thread来获取ThreadLocalMap
移除
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
主动移除是个好的处理方式,在不使用变量时应该主动移除
到这里ThreadLocal自己的功能已经介绍完了,能够理解为变量访问工具,这个变量的访问被控制到只能当前线程有权限访问,其余线程无权限。
代码示例
简单示例
package cn.tinyice.demo.thread; import java.util.Random; import java.util.concurrent.TimeUnit; /** * ThreadLocalDemo * * @author Tinyice */ public class ThreadLocalDemo { private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); private static final Random random=new Random(); /** * 内存模型示例 */ private int mod=0; public ThreadLocalDemo(int mod) { this.mod = mod; } public int getMod() { return mod; } public void add() { mod+=1; // 每一个线程的值是不同的 threadLocal.set(get() + random.nextInt(10)); } public Integer get() { Integer integer = threadLocal.get(); return null == integer ? 0 : integer; } public static void main(String[] args) { ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(0); for (int i = 0; i < 1000; i++) { new ThreadLocalThread(threadLocalDemo).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("------------------------------------------------"); System.out.println(String.format("threadId=%d threadLocal value=%d mod=%d", Thread.currentThread().getId(), threadLocalDemo.get(),threadLocalDemo.getMod())); } } class ThreadLocalThread extends Thread { private ThreadLocalDemo threadLocalDemo; public ThreadLocalThread( ThreadLocalDemo threadLocalDemo) { this.threadLocalDemo = threadLocalDemo; } @Override public void run() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } threadLocalDemo.add(); System.out.println(String.format("threadId=%d threadLocal value=%d", Thread.currentThread().getId(), threadLocalDemo.get())); } }
控制台:
threadId=853 threadLocal value=3 threadId=848 threadLocal value=9 threadId=808 threadLocal value=9 threadId=121 threadLocal value=8 ------------------------------------------------ threadId=1 threadLocal value=0 mod=999 Process finished with exit code 0
mod=999 是可见性问题形成,而value值都不同则说明不一样线程的变量值不同。
Spring示例
public abstract class TransactionSynchronizationManager { private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active"); ... }
public final class DateTimeContextHolder { private static final ThreadLocal<DateTimeContext> dateTimeContextHolder = new NamedThreadLocal<>("DateTimeContext"); ... }