ThreadLocal概念
ThreadLocal 字面意思来看有点像“线程的本地实现版本”,实际上真正含义是ThreadLocalVariable(线程本地局部变量),所以把它命名为ThreadLocalVar更加合适。
ThreadLocal 是用来解决共享对象(单个线程内共享)的多线程访问问题的,使用场合主要解决多线程中数据因并发产生不一致问题。
ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但是确避免线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
只要使用了“池”(线程池、连接池),再使用ThreadLocal时,尤其需要注意,每个线程在使用ThreadLocal的时候,必须对ThreadLocal执行一次clear操作,避免出现线程污染问题,这也是最常踩的坑(近期我们就遇到过2次类似情况)。
ThreadLocal与多线程
ThreadLocal和Synchonized都用于解决多线程并发访问问题。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离,它们处理不同的问题域。
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程单线程排队等待访问,而后者为每一个线程都提供了一份变量,因此可以互不影响的同时访问。
ThreadLocal导致的内存泄露
ThreadLocal 的生命周期和它相应的线程直接关联。如果线程被终止并且被垃圾回收器收集,它相应的ThreadLocal 变量也将会被回收。
内存问题主要发生在当ThreadLocal变量使用在运行在应用服务器上的Java EE应用程序里边时。应用服务器通过使用线程池来管理线程以保证资源安全和提高性能。(参见Tomcat HTTP conncector配置为例)。
例如,一个HttpServletRequest发送到应用服务器的ServletEngine,一个空闲的线程将会从线程池中取出并且和servlet的应用逻辑进行连接。如果这个servlet或者它调用的Java类正在使用ThreadLocal变量,这些变量将会和当前的工作线程连接。如果servlet完成并将相应发送给客户端,那么与之连接的线程会被返回到线程池中,以便用来处理其他的请求。这意味着线程对象及其相关联的ThreadLocal变量没有被垃圾回收器收集,因为其线程对象还存在着。
根据池中的线程数量(在运行环境中大于100个线程是正常的)以及ThreadLocal变量中对象的大小,可能会发生致命的内存问题。例如对线程池中的200个线程进行配置以及将ThreadLocal变量的大小设置为5MB,这将会导致有1GB的堆空间被这些变量所占用。这将会导致一个GC的开销并且可能会由于OutOfMemoryError导致JVM崩溃。