使用ThreadLocal可以维持线程封闭性,使线程中的某个值与保存值的对象关联,防止对可变的单例变量或全局变量进行共享,但使用不当也会造成内存泄漏,先了解它,再使用它。
SimpleDateFormat是我们常用的日期格式化工具,但熟悉的朋友都知道它是线程不安全的。
1 2 3 4 5 6 7 public class Acuptest { public static void main (String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS" ); System.out.println(sdf.format(new Date())); } }
上面的用法完全没有问题,但现在spring无处不在,很多类都是以bean的形式存在于spring容器被各种共享,一不小心就会写成下面这种样子。
1 2 3 4 5 6 7 8 public class Acuptest { private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS" ); public String format (Date date) { return sdf.format(date); } }
只是这样还看不出什么问题,但既然提到了SimpleDateFormat是线程不安全的,那么就看看为什么不安全。
进入源码,只看关键部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public abstract class DateFormat extends Format { protected Calendar calendar; public abstract StringBuffer format (Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) ; public final String format (Date date) { return format(date, new StringBuffer(), DontCareFieldPosition.INSTANCE).toString(); } } public class SimpleDateFormat extends DateFormat { @Override public StringBuffer format (Date date, StringBuffer toAppendTo, FieldPosition pos) { pos.beginIndex = pos.endIndex = 0 ; return format(date, toAppendTo, pos.getFieldDelegate()); } private StringBuffer format (Date date, StringBuffer toAppendTo, FieldDelegate delegate) { calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); } }
使用局部变量 只要不让多线程访问同一个对象,每次要用就new一个对象即可。
使用ThreadLocal 很多时候某些对象往往不适合频繁创建、销毁,但它又像SimpleDateFormat那样线程不安全。这时候ThreadLocal就有用武之地了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Acuptest { private ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue () { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS" ); } }; public String format (Date date) { return sdf.get().format(date); }
ThreadLocal源码分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class ThreadLocal <T > { 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(); } ThreadLocalMap getMap (Thread t) { return t.threadLocals; } 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; } protected T initialValue () { return null ; } }
Thread源码分析 上面的源码中看到ThreadLocal多次使用Thread中的成员变量threadLocals,于是对Thread对象的结构再做个简单了解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null ; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null ; } public class ThreadLocal <T > { static class ThreadLocalMap { static class Entry extends WeakReference <ThreadLocal <?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super (k); value = v; } } private Entry[] table; } }
threadLocals和inheritableThreadLocals 从Thread源码中可以看到ThreadLocal.ThreadLocalMa类型的成员变量有两个,有个是之前没有见过的inheritableThreadLocals,这个变量不是给ThreadLocal用的,而是给另一个类似的工具InheritableThreadLocal用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class InheritableThreadLocal <T > extends ThreadLocal <T > { protected T childValue (T parentValue) { return parentValue; } ThreadLocalMap getMap (Thread t) { return t.inheritableThreadLocals; } void createMap (Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this , firstValue); } }
从源码上看,InheritableThreadLocal继承了ThreadLocal,然后使用的MAP换了,其他就没什么特别的。
但InheritableThreadLocal有着特殊的功能:它可以使用父线程的inheritableThreadLocals变量,实现父子线程共享变量。
InheritableThreadLocal为什么可以让子线程使用父线程的变量,关键的地方不在它,而在Thread类的初始化流程,Thread初始化时,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Thread implements Runnable { private void init (ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { Thread parent = currentThread(); if (inheritThreadLocals && parent.inheritableThreadLocals != null ) this .inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } }
ThreadLocal中的弱引用(WeakReference) 从上面的源码中注意到: ThreadLocal.ThreadLocalMap.Entry extends WeakReference<ThreadLocal<?>>
Entry的key是ThreadLocal<?>,是个弱引用(被GC扫描到就回收)。如果不这样,当ThreadLocal<?>用完了,但线程还没结束,因此Thread里面还持有着ThreadLocal<?>的强引用,那么它永远不会被回收,可以认为内存泄漏了。
ThreadLocal的内存泄漏 就算是使用了弱引用,依然存在内存泄漏的可能。因为弱引用仅仅是Entry的key(ThreadLocal),value(泛型T)并不是弱引用。最终可能出现的结果就是,ThreadLocal被回收了,Thread里的MAP中KEY就没了,但value还在,这样一来这个value永远不会被get()方法返回,确又存在于内存不愿消散。
内部实现尽量避免内存泄漏:
在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。
如果没有调用这些方法去触发这个过程,依然会内存泄漏,所以在线程用完这个对象后,可以显示调用remove方法使其清除。