原创文章,始自发做者我的博客,转载请务必将下面这段话置于文章开头处(保留超连接)。
本文转发自技术世界,原文连接 http://www.jasongj.com/java/threadlocal/java
因为 ThreadLocal 支持范型,如 ThreadLocal< StringBuilder >,为表述方便,后文用 变量 表明 ThreadLocal 自己,而用 实例 表明具体类型(如 StringBuidler )的实例。react
写这篇文章的一个缘由在于,网上不少博客关于 ThreadLocal 的适用场景以及解决的问题,描述的并不清楚,甚至是错的。下面是常见的对于 ThreadLocal的介绍安全
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路
ThreadLocal的目的是为了解决多线程访问资源时的共享问题session
还有不少文章在对比 ThreadLocal 与 synchronize 的异同。既然是做比较,那应该是认为这二者解决相同或相似的问题。多线程
上面的描述,问题在于,ThreadLocal 并不解决多线程 共享 变量的问题。既然变量不共享,那就更谈不上同步的问题。并发
ThreadLoal 变量,它的基本原理是,同一个 ThreadLocal 所包含的对象(对ThreadLocal< String >而言即为 String 类型变量),在不一样的 Thread 中有不一样的副本(实际是不一样的实例,后文会详细阐述)。这里有几点须要注意app
那 ThreadLocal 到底解决了什么问题,又适用于什么样的场景?less
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).ide
核心意思是ui
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每一个使用该变量的线程都会初始化一个彻底独立的实例副本。ThreadLocal 变量一般被
private static
修饰。当一个线程结束时,它所使用的全部 ThreadLocal 相对的实例副本均可被回收。
总的来讲,ThreadLocal 适用于每一个线程须要本身独立的实例且该实例须要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。后文会经过实例详细阐述该观点。另外,该场景下,并不是必须使用 ThreadLocal ,其它方式彻底能够实现一样的效果,只是 ThreadLocal 使得实现更简洁。
下面经过以下代码说明 ThreadLocal 的使用方式
public class ThreadLocalDemo { public static void main(String[] args) throws InterruptedException { int threads = 3; CountDownLatch countDownLatch = new CountDownLatch(threads); InnerClass innerClass = new InnerClass(); for(int i = 1; i <= threads; i++) { new Thread(() -> { for(int j = 0; j < 4; j++) { innerClass.add(String.valueOf(j)); innerClass.print(); } innerClass.set("hello world"); countDownLatch.countDown(); }, "thread - " + i).start(); } countDownLatch.await(); } private static class InnerClass { public void add(String newStr) { StringBuilder str = Counter.counter.get(); Counter.counter.set(str.append(newStr)); } public void print() { System.out.printf("Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n", Thread.currentThread().getName(), Counter.counter.hashCode(), Counter.counter.get().hashCode(), Counter.counter.get().toString()); } public void set(String words) { Counter.counter.set(new StringBuilder(words)); System.out.printf("Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n", Thread.currentThread().getName(), Counter.counter.hashCode(), Counter.counter.get().hashCode(), Counter.counter.get().toString()); } } private static class Counter { private static ThreadLocal<StringBuilder> counter = new ThreadLocal<StringBuilder>() { @Override protected StringBuilder initialValue() { return new StringBuilder(); } }; } }
ThreadLocal自己支持范型。该例使用了 StringBuilder 类型的 ThreadLocal 变量。可经过 ThreadLocal 的 get() 方法读取 StringBuidler 实例,也可经过 set(T t) 方法设置 StringBuilder。
上述代码执行结果以下
Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0 Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:01 Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:01 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:012 Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1609588821, Value:0123 Set, Thread name:thread - 3 , ThreadLocal hashcode:372282300, Instance hashcode:1362597339, Value:hello world Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:01 Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:012 Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:012 Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:418873098, Value:0123 Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1780437710, Value:0123 Set, Thread name:thread - 1 , ThreadLocal hashcode:372282300, Instance hashcode:482932940, Value:hello world Set, Thread name:thread - 2 , ThreadLocal hashcode:372282300, Instance hashcode:1691922941, Value:hello world
从上面的输出可看出
既然每一个访问 ThreadLocal 变量的线程都有本身的一个“本地”实例副本。一个可能的方案是 ThreadLocal 维护一个 Map,键是 Thread,值是它在该 Thread 内的实例。线程经过该 ThreadLocal 的 get() 方案获取实例时,只须要以线程为键,从 Map 中找出对应的实例便可。该方案以下图所示
该方案可知足上文提到的每一个线程内一个独立备份的要求。每一个新线程访问该 ThreadLocal 时,须要向 Map 中添加一个映射,而每一个线程结束时,应该清除该映射。这里就有两个问题:
其中锁的问题,是 JDK 未采用该方案的一个缘由。
上述方案中,出现锁的问题,缘由在于多线程访问同一个 Map。若是该 Map 由 Thread 维护,从而使得每一个 Thread 只访问本身的 Map,那就不存在多线程写的问题,也就不须要锁。该方案以下图所示。
该方案虽然没有锁的问题,可是因为每一个线程访问某 ThreadLocal 变量后,都会在本身的 Map 内维护该 ThreadLocal 变量与具体实例的映射,若是不删除这些引用(映射),则这些 ThreadLocal 不能被回收,可能会形成内存泄漏。后文会介绍 JDK 如何解决该问题。
该方案中,Map 由 ThreadLocal 类的静态内部类 ThreadLocalMap 提供。该类的实例维护某个 ThreadLocal 与具体实例的映射。与 HashMap 不一样的是,ThreadLocalMap 的每一个 Entry 都是一个对 键 的弱引用,这一点从super(k)
可看出。另外,每一个 Entry 都包含了一个对 值 的强引用。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
使用弱引用的缘由在于,当没有强引用指向 ThreadLocal 变量时,它可被回收,从而避免上文所述 ThreadLocal 不能被回收而形成的内存泄漏的问题。
可是,这里又可能出现另一种内存泄漏的问题。ThreadLocalMap 维护 ThreadLocal 变量与具体实例的映射,当 ThreadLocal 变量被回收后,该映射的键变为 null,该 Entry 没法被移除。从而使得实例被该 Entry 引用而没法被回收形成内存泄漏。
注:Entry虽然是弱引用,但它是 ThreadLocal 类型的弱引用(也即上文所述它是对 键 的弱引用),而非具体实例的的弱引用,因此没法避免具体实例相关的内存泄漏。
读取实例方法以下所示
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(); }
读取实例时,线程首先经过getMap(t)
方法获取自身的 ThreadLocalMap。从以下该方法的定义可见,该 ThreadLocalMap 的实例是 Thread 类的一个字段,即由 Thread 维护 ThreadLocal 对象与具体实例的映射,这一点与上文分析一致。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
获取到 ThreadLocalMap 后,经过map.getEntry(this)
方法获取该 ThreadLocal 在当前线程的 ThreadLocalMap 中对应的 Entry。该方法中的 this 即当前访问的 ThreadLocal 对象。
若是获取到的 Entry 不为 null,从 Entry 中取出值即为所需访问的本线程对应的实例。若是获取到的 Entry 为 null,则经过setInitialValue()
方法设置该 ThreadLocal 变量在该线程中对应的具体实例的初始值。
设置初始值方法以下
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
该方法为 private 方法,没法被重载。
首先,经过initialValue()
方法获取初始值。该方法为 public 方法,且默认返回 null。因此典型用法中经常重载该方法。上例中即在内部匿名类中将其重载。
而后拿到该线程对应的 ThreadLocalMap 对象,若该对象不为 null,则直接将该 ThreadLocal 对象与对应实例初始值的映射添加进该线程的 ThreadLocalMap中。若为 null,则先建立该 ThreadLocalMap 对象再将映射添加其中。
这里并不须要考虑 ThreadLocalMap 的线程安全问题。由于每一个线程有且只有一个 ThreadLocalMap 对象,而且只有该线程本身能够访问它,其它线程不会访问该 ThreadLocalMap,也即该对象不会在多个线程中共享,也就不存在线程安全的问题。
除了经过initialValue()
方法设置实例的初始值,还可经过 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); }
该方法先获取该线程的 ThreadLocalMap 对象,而后直接将 ThreadLocal 对象(即代码中的 this)与目标实例的映射添加进 ThreadLocalMap 中。固然,若是映射已经存在,就直接覆盖。另外,若是获取到的 ThreadLocalMap 为 null,则先建立该 ThreadLocalMap 对象。
对于已经再也不被使用且已被回收的 ThreadLocal 对象,它在每一个线程内对应的实例因为被线程的 ThreadLocalMap 的 Entry 强引用,没法被回收,可能会形成内存泄漏。
针对该问题,ThreadLocalMap 的 set 方法中,经过 replaceStaleEntry 方法将全部键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。另外,会在 rehash 方法中经过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。经过这种方式,ThreadLocal 可防止内存泄漏。
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) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
如上文所述,ThreadLocal 适用于以下两种场景
对于第一点,每一个线程拥有本身实例,实现它的方式不少。例如能够在线程内部构建一个单独的实例。ThreadLoca 能够以很是方便的形式知足该需求。
对于第二点,能够在知足第一点(每一个线程有本身的实例)的条件下,经过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
对于 Java Web 应用而言,Session 保存了不少信息。不少时候须要经过 Session 获取信息,有些时候又须要修改 Session 的信息。一方面,须要保证每一个线程有本身单独的 Session 实例。另外一方面,因为不少地方都须要操做 Session,存在多方法共享 Session 的需求。若是不使用 ThreadLocal,能够在每一个线程内构建一个 Session实例,并将该实例在多个方法间传递,以下所示。
public class SessionHandler { @Data public static class Session { private String id; private String user; private String status; } public Session createSession() { return new Session(); } public String getUser(Session session) { return session.getUser(); } public String getStatus(Session session) { return session.getStatus(); } public void setStatus(Session session, String status) { session.setStatus(status); } public static void main(String[] args) { new Thread(() -> { SessionHandler handler = new SessionHandler(); Session session = handler.createSession(); handler.getStatus(session); handler.getUser(session); handler.setStatus(session, "close"); handler.getStatus(session); }).start(); } }
该方法是能够实现需求的。可是每一个须要使用 Session 的地方,都须要显式传递 Session 对象,方法间耦合度较高。
这里使用 ThreadLocal 从新实现该功能以下所示。
public class SessionHandler { public static ThreadLocal<Session> session = new ThreadLocal<Session>(); @Data public static class Session { private String id; private String user; private String status; } public void createSession() { session.set(new Session()); } public String getUser() { return session.get().getUser(); } public String getStatus() { return session.get().getStatus(); } public void setStatus(String status) { session.get().setStatus(status); } public static void main(String[] args) { new Thread(() -> { SessionHandler handler = new SessionHandler(); handler.getStatus(); handler.getUser(); handler.setStatus("close"); handler.getStatus(); }).start(); } }
使用 ThreadLocal 改造后的代码,再也不须要在各个方法间传递 Session 对象,而且也很是轻松的保证了每一个线程拥有本身独立的实例。
若是单看其中某一点,替代方法不少。好比可经过在线程内建立局部变量可实现每一个线程有本身的实例,使用静态变量可实现变量在方法间的共享。但若是要同时知足变量在线程间的隔离与方法间的共享,ThreadLocal再合适不过。