问题描述
[ 由于SimpleDateForamt是非线程安全的,所以想用ThreadLocal封装一下,但是同事说这样写会造成内存泄露,要请教一下各位,下面这样写会造成内存泄露么,烦请大伙给指点指点,不胜感激。(环境springmvc + spring +ibatis,使用了线程池)public class DateUtil {private static ThreadLocal<DateFormat> sdf = new ThreadLocal<DateFormat>() {protected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");};};public static DateFormat getDateFormat(ThreadLocal<DateFormat> tl) {return tl.get();}public static String format(Date date) {if (date == null) {return "";}return getDateFormat(sdf).format(date);}public static Date parse(String st) throws ParseException {return getDateFormat(sdf).parse(st);}}
解决方案
要整明白你的问题,你需要先搞懂几个技术点,1: ThreadLocal的底层原理http://www.iteye.com/topic/103804 这有一个文章你可以参考,其实最好的办法就是自己看源码。这里一个关键性的问题就是你要清楚ThreadLocal内部并没有Map,而是线程对象内部有一个Map,而ThreadLocal是把自己当做Key,去访问线程对象当中的Map2: Web环境的建成池,也就是说Web服务器会有一个线程池,每次请求到达服务器端的时候就会从里面拿一个线程(假设线程A)去完成这次请求,那么当执行到你上面代码的时候就会往当前线程的MAP里放一个FORMAT对象,那么假设又有很多次请求由不同的线程完成(B,C,D) 那么他们的MAP当中同样都会放入一个FORMAT对象,然后再请求结束以后这些线程又回到线程池当中等待下次请求。然后紧接着又有请求过来了,假设这时候又是线程A去处理,那么当在执行上面的代码的时候{return tl.get()},不会在去新创建对象了,而是把线程A当中之前设置进去的对象拿出来直接用,所以不会存在内存泄露的问题。但是有一个问题就是会导致WEB服务器线程池内的所有线程的MAP当中都有一个format对象。因为你在线程结束的时候并没有清除掉。
解决方案二:
JDK 文档:引用每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
解决方案三:
会有内存泄露,只有创建没有销毁。
解决方案四:
很抱歉,我的回答是错误的,如果误导了楼主,在此道歉,我犯了个想当然的错误,或者是记忆有误,把以前错误的想法写出来了。通过分析源码,可以知道,每个Thread对象都有如下对象,分别是给ThreadLocal和InheritableThreadLocal这两个对象用的。 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;然后在以上两个ThreadLocal相关的类中,他们在取数据的时候,是以当前的ThreadLocal做为key去这map中取value的,并且在线程退出的时候,JVM会调用Thread类的一个私有方法,让此线程有机会去清除一些内部使用的资源,其中就包括了以上两个map,源码如下: /** * This method is called by the system to give a Thread * a chance to clean up before it actually exits. */ private void exit() { if (group != null) { group.threadTerminated(this); group = null; } /* Aggressively null out all reference fields: see bug 4006245 */ target = null; /* Speed the release of some of these resources */ threadLocals = null; inheritableThreadLocals = null; inheritedAccessControlContext = null; blocker = null; uncaughtExceptionHandler = null; }并且在ThreadLocal的实现中,为了不强引用ThreadLocal对象,采用了一个Entry对象以弱引用的方式引用着ThreadLocal对象,并以强引用的方式引用着value。再次表示抱歉,误导了楼主以及其他看客。
解决方案五:
首先,这种做法没有额外开销,因为只有到get的时候,才会真正初始化DateFormat。也不会泄露,不会出现所谓“Thread对象也没有办法GC”的情况,因为是Thread对象本身维护的Map,而不是ThreadLocal。
解决方案六:
每个请求一个线程,每个线程有自己的ThreadLocalMap存new SimpleDateFormat()如果有100个请求是不就有好几百个对象(甚至更多)的开销?
解决方案七:
首先假设你这个是web项目,当一个请求来的时候,web服务器会用一个线程去服务这个请求,这个时候,你就会为此线程绑定一个SimpleDateFormat对象在ThreadLocal里面,所以随着请求的越来越多,有可能,只是有可能,你的ThreadLoad里面的SimpleDateFormat对象会越来越多,说有可能是因为WEB服务器会有一个线程池,但是线程池并不代表线程就永远不会结束,当一个线程结束了的时候,你没有从ThreadLocal中移除对应的SimpleDateFormat,并且你看了ThreadLocal的实现你就知道,里面维护着一个Map,key就是对应的线程,所以长久以后,就会造成Thread由于有强引用,所以它即使退出了,Thread对象也没有办法GC,他对应的value,也就是SimpleDateFormat也没有办法回收,长久下来肯定就会内存泄漏了。其实要解决也挺简单的,只需要保证remove方法会被调用就可以了,你可以在Filter里面保证调用一下remove就可以防止内存泄漏了。
解决方案八:
这篇文章分析的ThreadLocal内存泄露分析http://liuinsect.iteye.com/blog/1827012