为什么threadlocalmap可能引起java map 内存泄露露

在最近一个项目中,在项目发布之后,发现系统中有内存泄漏问题。表象是堆内存随着系统的运行时间缓慢增长,一直没有办法通过gc来回收,最终于导致堆内存耗尽,内存溢出。开始是怀疑ThreadLocal的问题,因为在项目中,大量使用了线程的ThreadLocal保存线程上下文信息,在正常情况下,在线程开始的时候设置线程变量,在线程结束的时候,需要清除线程上下文信息,如果线程变量没有清除,会导致线程中保存的对象无法释放。
从这个正常的情况来看,假设没有清除线程上下文变量,那么在线程结束的时候(线程销毁),线程上下文变量所占用的内存会随着线程的销毁而被回收。至少从程序设计者角度来看,应该如此。实际情况下是怎么样,需要进行测试。
但是对于web类型的应用,为了避免产生大量的线程产生堆栈溢出(默认情况下一个线程会分配512K的栈空间),都会采用线程池的设计方案,对大量请求进行负载均衡。所以实际应用中,一般都会是线程池的设计,处理业务的线程数一般都在200以下,即使所有的线程变量都没有清理,那么理论上会出现线程保持的变量最大数是200,如果线程变量所指示的对象占用比较少(小于10K),200个线程最多只有2M(200*10K)的内存无法进行回收(因为线程池线程是复用的,每次使用之前,都会从新设置新的线程变量,那么老的线程变量所指示的对象没有被任何对象引用,会自动被垃圾回收,只有最后一次线程被使用的情况下,才无法进行回收)。
以上只是理论上的分析,那么实际情况下如何了,我写了一段代码进行实验。
硬件配置:
处理器名称: Intel Core i7&2.3 GHz &4核
内存: 16 GB
操作系统:OS X 10.8.2
java版本:"1.7.0_04-ea"
-Xms128M -Xmx512M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -Xloggc:gc.log
测试代码:Test.java&
import java.io.BufferedR
import java.io.InputStreamR
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
public class Test {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int testCase= Integer.parseInt(br.readLine());
br.close();
switch(testCase){
// 测试情况1. 无线程池,线程不休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出
case 1 :testWithThread(true, 0); break;
// 测试情况2. 无线程池,线程不休眠,没有清除thread_local 里面的线程变量;测试结果:无内存溢出
case 2 :testWithThread(false, 0); break;
// 测试情况3. 无线程池,线程休眠1000毫秒,清除thread_local里面的线程的线程变量;测试结果:无内存溢出,但是新生代内存整体使用高
case 3 :testWithThread(false, 1000); break;
// 测试情况4. 无线程池,线程永久休眠(设置最大值),清除thread_local里面的线程的线程变量;测试结果:无内存溢出
case 4 :testWithThread(true, Integer.MAX_VALUE); break;
// 测试情况5. 有线程池,线程池大小50,线程不休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出
case 5 :testWithThreadPool(50,true,0); break;
// 测试情况6. 有线程池,线程池大小50,线程不休眠,没有清除thread_local 里面的线程变量;测试结果:无内存溢出
case 6 :testWithThreadPool(50,false,0); break;
// 测试情况7. 有线程池,线程池大小50,线程无限休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出
case 7 :testWithThreadPool(50,true,Integer.MAX_VALUE); break;
// 测试情况8. 有线程池,线程池大小1000,线程无限休眠,并且清除thread_local 里面的线程变量;测试结果:无内存溢出
case 8 :testWithThreadPool(1000,true,Integer.MAX_VALUE); break;
default :break;
public static void testWithThread(boolean clearThreadLocal, long sleepTime) {
while (true) {
Thread.sleep(100);
new Thread(new TestTask(clearThreadLocal, sleepTime)).start();
} catch (Exception e) {
e.printStackTrace();
public static void testWithThreadPool(int poolSize,boolean clearThreadLocal, long sleepTime) {
ExecutorService service = Executors.newFixedThreadPool(poolSize);
while (true) {
Thread.sleep(100);
service.execute(new TestTask(clearThreadLocal, sleepTime));
} catch (Exception e) {
e.printStackTrace();
public static final byte[] allocateMem() {
// 这里分配一个1M的对象
byte[] b = new byte[1024 * 1024];
static class TestTask implements Runnable {
/** 是否清除上下文参数变量 */
private boolean clearThreadL
/** 线程休眠时间 */
private long sleepT
public TestTask(boolean clearThreadLocal, long sleepTime) {
this.clearThreadLocal = clearThreadL
this.sleepTime = sleepT
public void run() {
ThreadLocalHolder.set(allocateMem());
// 大于0的时候才休眠,否则不休眠
if (sleepTime & 0) {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
} finally {
if (clearThreadLocal) {
ThreadLocalHolder.clear();
ThreadLocalHolder.java
public class ThreadLocalHolder {
public static final ThreadLocal&Object& threadLocal = new ThreadLocal&Object&();
public static final void set(byte [] b){
threadLocal.set(b);
public static final void clear(){
threadLocal.set(null);
测试结果分析:
无线程池的情况:测试用例1-4
下面是测试用例1 的垃圾回收日志
下面是测试用例2 的垃圾回收日志
对比分析测试用例1 和 测试用例2 的GC日志,发现基本上都差不多,说明是否清楚线程上下文变量不影响垃圾回收,对于无线程池的情况下,不会造成内存泄露
对于测试用例3,由于业务线程sleep 一秒钟,会导致业务系统中有产生大量的阻塞线程,理论上新生代内存会比较高,但是会保持到一定的范围,不会缓慢增长,导致内存溢出,通过分析了测试用例3的gc日志,发现符合理论上的分析,下面是测试用例3的垃圾回收日志
通过上述日志分析,发现老年代产生了一次垃圾回收,可能是开始大量线程休眠导致内存无法释放,这一部分线程持有的线程变量会在重新唤醒之后运行结束被回收,新生代的内存内存一直维持在4112K,也就是4个线程持有的线程变量。
对于测试用例4,由于线程一直sleep,无法对线程变量进行释放,导致了内存溢出。
有线程池的情况:测试用例5-8
对于测试用例5,开设了50个工作线程,每次使用线程完成之后,都会清除线程变量,垃圾回收日志和测试用例1以及测试用例2一样。
对于测试用例6,也开设了50个线程,但是使用完成之后,没有清除线程上下文,理论上会有50M内存无法进行回收,通过垃圾回收日志,符合我们的语气,下面是测试用例6的垃圾回收日志
通过日志分析,发现老年代回收比较频繁,主要是因为50个线程持有的50M空间一直无法彻底进行回收,而新生代空间不够(我们设置的是128M内存,新生代大概36M左右)。所有整体内存的使用量肯定一直在50M之上。
对于测试用例7,由于工作线程最多50个,即使线程一直休眠,再短时间内也不会导致内存溢出,长时间的情况下会出现内存溢出,这主要是因为任务队列空间没有限制,和有没有清除线程上下文变量没有关系,如果我们使用的有限队列,就不会出现这个问题。
对于测试用例8,由于工作线程有1000个,导致至少1000M的堆空间被使用,由于我们设置的最大堆是512M,导致结果溢出。系统的堆空间会从开始的128M逐步增长到512M,最后导致溢出,从gc日志来看,也符合理论上的判断。由于gc日志比较大,就不在贴出来了。
所以从上面的测试情况来看,线上上下文变量是否导致内存泄露,是需要区分情况的,如果线程变量所占的空间的比较小,小于10K,是不会出现内存泄露的,导致内存溢出的。如果线程变量所占的空间比较大,大于1M的情况下,出现的内存泄露和内存溢出的情况比较大。以上只是jdk1.7版本情况下的分析,个人认为jdk1.6版本的情况和1.7应该差不多,不会有太大的差别。
-----------------------下面是对ThreadLocal的分析-------------------------------------
对于ThreadLocal的概念,很多人都是比较模糊的,只知道是线程本地变量,而具体这个本地变量是什么含义,有什么作用,如何使用等很多java开发工程师都不知道如何进行使用。从JDK的对ThreadLocal的解释来看
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,
它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。&
ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个&Map&为每个线程复制一个变量的&拷贝&存储其中。每一个内部线程都有一个ThreadLocalMap对象。
当线程调用ThreadLocal.set(T object)方法设置变量时,首先获取当前线程引用,然后获取线程内部的ThreadLocalMap对象,设置map的key值为threadLocal对象,value为参数中的object。
当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以threadLocal对象为key去获取响应的ThreadLocalMap,如果此&Map&不存在则初始化一个,否则返回其中的变量。
也就是说每个线程内部的&ThreadLocalMap对象中的key保存的threadLocal对象的引用,从ThreadLocalMap的源代码来看,对threadLocal的对象的引用是WeakReference,也就是弱引用。
下面一张图描述这三者的整体关系
对于一个正常的Map来说,我们一般会调用Map.clear方法来清空map,这样map里面的所有对象就会释放。调用map.remove(key)方法,会移除key对应的对象整个entry,这样key和value 就不会任何对象引用,被java虚拟机回收。
而Thread对象里面的ThreadLocalMap里面的key是ThreadLocal的对象的弱引用,如果ThreadLocal对象会回收,那么ThreadLocalMap就无法移除其对应的value,那么value对象就无法被回收,导致内存泄露。但是如果thread运行结束,整个线程对象被回收,那么value所引用的对象也就会被垃圾回收。
什么情况下&ThreadLocal对象会被回收了,典型的就是ThreadLocal对象作为局部对象来使用或者每次使用的时候都new了一个对象。所以一般情况下,ThreadLocal对象都是static的,确保不会被垃圾回收以及任何时候线程都能够访问到这个对象。
&写了下面一段代码进行测试,发现两个方法都没有导致内存溢出,对于没有使用线程池的方法来说,因为每次线程运行完就退出了,Map里面引用的所有对象都会被垃圾回收,所以没有关系,但是为什么线程池的方案也没有导致内存溢出了,主要原因是ThreadLocal.set方法的实现,会做一个将Key== null 的元素清理掉的工作。导致线程之前由于ThreadLocal对象回收之后,ThreadLocalMap中的value 也会被回收,可见设计者也注意到这个地方可能出现内存泄露,为了防止这种情况发生,从而清空ThreadLocalMap中null为空的元素。
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
public class ThreadLocalLeakTest {
public static void main(String[] args) {
// 如果控制线程池的大小为50,不会导致内存溢出
testWithThreadPool(50);
// 也不会导致内存泄露
testWithThread();
static class TestTask implements Runnable {
public void run() {
ThreadLocal tl = new ThreadLocal();
// 确保threadLocal为局部对象,在退出run方法之后,没有任何强引用,可以被垃圾回收
tl.set(allocateMem());
public static void testWithThreadPool(int poolSize) {
ExecutorService service = Executors.newFixedThreadPool(poolSize);
while (true) {
Thread.sleep(100);
service.execute(new TestTask());
} catch (Exception e) {
e.printStackTrace();
public static void testWithThread() {
Thread.sleep(100);
} catch (InterruptedException e) {
new Thread(new TestTask()).start();
public static final byte[] allocateMem() {
// 这里分配一个1M的对象
byte[] b = new byte[1024 * 1024 * 1];
阅读(...) 评论()ThreadLocal - 简书
ThreadLocal
ThreadLocal的作用
ThreadLocal是为了隔离多个线程的数据共享。每个线程拥有自己的对象,不会和其他线程发生数据竞争。另外,它们之间也不应该发生数据竞争,因为既然使用了ThreadLocal,说明每个线程都有自己的数据,并且和其他的线程不同。所以,ThreadLocal并不是为了解决数据竞争的问题,解决数据竞争是指用同步、锁等来协调多个线程对同一个数据的访问,而使用ThreadLocal时,每个线程存储的是不同的数据。它只是能够在不同的线程下,通过同一个ThreadLocal访问到各自的数据。
ThreadLocal的方法
public ThreadLocal()
protected T initialValue()
// 初始化值。一般可以扩展 ThreadLocal类,并重载该方法。默认为 null。
public T get()
public void set(T value )
public void remove ()
ThreadLocal的实现原理
首先,每个线程中有一个ThreadLocalMap类型的成员变量threadLocals。threadLocals的key为ThreadLocal,值为该线程对应的值。所以,每个线程的值保存在线程内部,而不是ThreadLocal中。
ThreadLocal的作用场景
ThreadLocal经常作为private static变量。当它所在的类需要被多个线程使用,而每个线程使用各自的数据时,应该使用ThreadLocal。
ThreadLocal可能引发的内存泄漏
static class ThreadLocalMap {
static class Entry extends WeakReference &ThreadLocal&?&&
/** The value associated with this ThreadLocal. */
Entry (ThreadLocal&?& k, Object v ) {
super(k );
首先,可以看到,Entry扩展WeakReference,其中Key为WeakReference,而Value为强引用。
所以,当ThreadLocal没有外部强引用时,当系统gc时,ThreadLocal会被回收,key为null,那么对应的value就不能被访问,也不会被释放(除非Thread被销毁)。但是,ThreadLocalMap的实现中,有一些避免内存泄漏的方法。在调用ThreadLocal的get或set方法时,会根据hashcode寻找key的位置,在寻找的过程中,如果发现key为null,而value不为null,会释放value的强引用。当然,在不能保证不存在内存泄漏,所以在不用时,调用ThreadLocal的remove方法来防止内存泄漏。ThreadLocal的内存泄露
ThreadLocal的目的就是为每一个使用ThreadLocal的线程都提供一个值,让该值和使用它的线程绑定,当然每一个线程都可以独立地改变它绑定的值。如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序.
关于的ThreadLocal更多内容,请参考《》。
在了ThreadLocal的后,我发现如果我们使用不恰当,可能造成内存泄露。经我测试,内存泄露的确存在。虽然该内存泄露,理论上上已经不算严重。
测试代码如下
ThreadLocalTest文件
package com.teleca.
public class ThreadLocalTest {
&public ThreadLocalTest()
&ThreadLocal&Content& tl=new ThreadLocal&Content& ();
&void start()
& System.out.println(&begin&);
& Content content=tl.get();
& if(content==null)
&& content= new Content();
&& tl.set(content);
& System.out.println(&try to release content data&);
& //tl.set(null);//@1
& //tl.remove();//@2
& content=//@4
& System.out.println(&request gc&);
& System.gc();
&& Thread.sleep(1000);
& } catch (InterruptedException e) {
&& // TODO Auto-generated catch block
&& e.printStackTrace();
& System.out.println(&end&);
class Content
&byte data[]=new byte[];
&protected void finalize()
& System.out.println(&I am released&);
try to release content data
request gc
注意我们尝试在@3和@4处,释放对tl和content的引用,以便JAVA回收content。但是测试结果表明还有对content的引用,以致它没有能被JAVA虚拟机回收。
我们必须把@1或@2处的代码打开,才能把让它让JAVA虚拟机回收content.推荐打开@2而不是@1
把@1或@2处的代码打开后的运行结果如下:
try to release content data
request gc
I am released
另外注意,@3其实并不影响运行结果。
事实上每个Thread实例都有一个ThreadLocalMap成员变量,它以ThreadLocal对象为key,以ThreadLocal绑定的对象为Value。
.我们调用ThreadLocal的set()方法,只是把要绑定的对象存放在当前线程的ThreadLocalMap成员变量中,以便下次通过get()方法取得它。
ThreadLocalMap和普通map的最大区别就是它的Entry是针对ThreadLocal弱引用的,即当ThreadLocal没有其他引用为空时,JVM就可以GC回收ThreadLocal,从而得到一个null的key。
关于ThreadLocalMap的更多内容请参考《》
ThreadlocalMap维护了ThreadLocal对象和其绑定对象之间的关系,这个ThreadLocalMap有threshold,当超过threshold时,
ThreadLocalMap会首先检查内部ThreadLocal引用(前文说过,ThreadLocal是弱引用可以释放)是否为null,如果存在null,那么把绑定对象的引用设置为null,以便释放ThreadLocal绑定的对象,这样就腾出了位置给新的ThreadLocal。如果不存在slate threadlocal,那么double threshold。
除此之外,还有两个机会释放掉已经废弃的ThreadLocal绑定的对象所占用的内存,
一、当hash算法得到的table index刚好是一个null 的key的threadlocal时,直接用新的ThreadLocal替换掉已经废弃的。
二、每次在ThreadLocalMap中存放ThreadLocal,hash算法没有命中既有Entry,需要新建一个Entry时,也调用cleanSomeSlots来遍历清理Entry数组中已经废弃的ThreadLocal绑定的对象的引用。
此外,当Thread本身销毁时,这个ThreadLocalMap也一定被销毁了(ThreadLocalMap是Thread对象的成员),
这样所有绑定到该线程的ThreadLocal的Object Value对象,如果在外部没被引用的话(通常是这样),也就没有任何引用继续保持,所以也就被销毁回收了。
从上可以看出已经充分考虑了时间和空间的权衡,但是因为置为null的ThreadLocal对应的Object Value在无外部引用时,任然无法及时回收。
ThreadLocalMap只有到达threshold时或添加entry时才做检查,不似gc是定时检查,
不过我们可以手工通过ThreadLocal的remove()方法或set(null)解除ThreadLocalMap对ThreadLocal绑定对象的引用,及时的清理废弃的threadlocal绑定对象的内存以。remove()往往还能做更多的清理工作,因此推荐使用它,而不使用set(null).
需要说明的是,只要不往不用的threadlocal中放入大量数据,问题不大,毕竟还有回收的机制。
被废弃了的ThreadLocal所绑定对象的引用,会在以下4情况被清理。
如果此时外部没有绑定对象的引用,则该绑定对象就能被回收了:
1 Thread结束时。
2 当Thread的ThreadLocalMap的threshold超过最大值时。
3 向Thread的ThreadLocalMap中存放一个ThreadLocal,hash算法没有命中既有Entry,而需要新建一个Entry时。
4 手工通过ThreadLocal的remove()方法或set(null)。
因此如果我们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也能被回收,因而产出内存泄露。问题现象:
1 线程池中的线程,内存占用持续增长,通过 Jvisualvm 观察进程运行状态,发现 JVM 老年代内存最终会被耗尽,进而导致进程频繁 Full GC,CPU 资源几乎全部用于垃圾回收。
2 通过MAT(Memory Analyzer Tool)分析heapdump文件,发现线程对象的保留集(retained set)多大。线程池中有1500个线程,线程对象的堆内存几乎全部被 threadlocals 对象占用。
1 threadlocals 在什么情况下会造成内存泄露?
当 ThreadLocals 对象通过 set 方法复制后,没有通过remove方法销毁,且线程由线程池管理,生命周期比较长时,将必然发生内存泄露。
2 通过查看 VMware vSphere 6.0 官方文档,编写简单的Demo 实例,连接到网络中的虚拟环境,并获取信息。
调试发现,当 new VimService() 创建服务时,会创建 XMLStreamReaderImpl对象。
由图可见,在创建VimService对象时,创建了XMLInputFactoryImpl实例(ThreadLocal对象),但是服务创建后,该ThreadLocal对象并没有通过remove函数销毁,导致无法通过垃圾回收机制回收线程中的内存资源。
通过函数栈可发现,内存泄露并不是VimService直接导致的,而是 com.sun.xml.internal.ws.wsdl.parser.RuntimeWSDLParser 类额 parse() 函数在进行 xml 解析时,没有合理释放 ThreadLocal 对象资源。由于很难查找到相应版本的源码文件且时间有限,没有对源文件进行深入分析,也因此没有定位发生
ThreadLocal 相关内存泄露的根本原因。
3 虚拟化 2.0 VS 虚拟化 3.0
Vim2.5 使用了 new VimService() 来创建服务,而老版本通过 new VimServiceLocator() 创建服务。
这也是为什么老版本能正常运行,而新版本发生内存泄露。
解决方案:
1 通过 Vim2.5 api 释放 ThreadLocal 资源:
经过多次尝试,并没有找到合适的接口!
由于时间关系,没有深入阅读Vim2.5和WSServiceDelegate的源码,或许有更正规的方式释放资源,请网友们继续探索。
2 通过反射机制,在线程层面主动释放 ThreadLocal 对象:
线程池中的线程会反复调用实现Runnable的类的run()方法,来执行任务。ThreadLocal对象的生命周期本来就应该在 run() 方法执行完成后结束。现在通过反射来手动清楚 ThreadLocal 对象。
源码如下:
private void cleanThreadLocals() {
// Get a reference to the thread locals table of the current thread
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField(&threadLocals&);
threadLocalsField.setAccessible(true);
Object threadLocalTable = threadLocalsField.get(thread);
// Get a reference to the array holding the thread local variables inside the
// ThreadLocalMap of the current thread
Class threadLocalMapClass = Class.forName(&java.lang.ThreadLocal$ThreadLocalMap&);
Field tableField = threadLocalMapClass.getDeclaredField(&table&);
tableField.setAccessible(true);
Object table = tableField.get(threadLocalTable);
// The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
// is a reference to the actual ThreadLocal variable
Field referentField = Reference.class.getDeclaredField(&referent&);
referentField.setAccessible(true);
for (int i=0; i & Array.getLength(table); i++) {
// Each entry in the table array of ThreadLocalMap is an Entry object
// representing the thread local reference and its value
Object entry = Array.get(table, i);
if (entry != null) {
// Get a reference to the thread local object and remove it from the table
ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry);
threadLocal.remove();
} catch(Exception e) {
// We will tolerate an exception here and just log it
throw new IllegalStateException(e);
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:11184次
排名:千里之外
原创:53篇
(10)(8)(17)(5)(8)(1)(4)(2)(1)(1)(1)& & &1、ThreadLocal是什么?有什么用?
& & &2、ThreadLocal源码简要总结?
& & &3、ThreadLocal为什么会导致内存泄漏?
二、ThreadLocal是什么?有什么用?
引入话题:在并发条件下,如何正确获得共享数据?举例:假设有多个用户需要获取用户信息,一个线程对应一个用户。在mybatis中,session用于操作数据库,那么设置、获取操作分别是session.set()、session.get(),如何保证每个线程都能正确操作达到想要的结果?
* 回顾synchronized在多线程共享线程的问题
* @author qiuyongAaron
public class ThreadLocalOne {
volatile Person person=new Person();
synchronized String setAndGet(String name){
//System.out.print(Thread.currentThread().getName()+":");
person.name=
//模拟网络延迟
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
return person.
public static void main(String[] args) {
ThreadLocalOne
threadLocal=new ThreadLocalOne();
new Thread(()-&System.out.println(threadLocal.setAndGet("arron")),"t1").start();
new Thread(()-&System.out.println(threadLocal.setAndGet("tony")),"t2").start();
class Person{
String name="tom";
public Person(String name) {
this.name=
public Person(){}
运行结果:
无synchronized:
有synchronized:
步骤分析:
无synchronized的时候,因为非原子操作,显然不是预想结果,可参考我关于synchronized的讨论。
现在,我们的需求是:每个线程独立的设置获取person信息,不被线程打扰。
因为,person是共享数据,用同步互斥锁synchronized,当一个线程访问共享数据的时候,其他线程堵塞,不再多余赘述。
通过举例问题,可能大家又会很疑惑?
mybatis、hibernate是如何实现的呢?
synchronized不会很消耗资源,当成千上万个操作的时候,承受并发不说,数据返回延迟如何确保用户体验?
ThreadLocal是什么?有什么用?
* 谈谈ThreadLocal的作用
* @author qiuyongAaron
public class ThreadLocalThree {
ThreadLocal&Person& threadLocal=new ThreadLocal&Person&();
public String setAndGet(String name){
threadLocal.set(new Person(name));
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
return threadLocal.get().
public static void main(String[] args) {
ThreadLocalThree
threadLocal=new ThreadLocalThree();
new Thread(()-&System.out.println("t1:"+threadLocal.setAndGet("arron")),"t1").start();
new Thread(()-&System.out.println("t2:"+threadLocal.setAndGet("tony")),"t2").start();
运行结果:
1、根据预期结果,那ThreadLocal到底是什么?
回顾Java内存模型:
&&&&&&在虚拟机中,堆内存用于存储共享数据(实例对象),堆内存也就是这里说的主内存。
& & &每个线程将会在堆内存中开辟一块空间叫做线程的工作内存,附带一块缓存区用于存储共享数据副本。那么,共享数据在堆内存当中,线程通信就是通过主内存为中介,线程在本地内存读并且操作完共享变量操作完毕以后,把值写入主内存。
ThreadLocal被称为线程局部变量,说白了,他就是线程工作内存的一小块内存,用于存储数据。
那么,ThreadLocal.set()、ThreadLocal.get()方法,就相当于把数据存储于线程本地,取也是在本地内存读取。就不会像synchronized需要频繁的修改主内存的数据,再把数据复制到工作内存,也大大提高访问效率。
2、ThreadLocal到底有什么用?
回到最开始的举例,也就等价于mabatis、hibernate为什么要使用threadlocal来存储session?
作用一:因为线程间的数据交互是通过工作内存与主存的频繁读写完成通信,然而存储于线程本地内存,提高访问效率,避免线程阻塞造成cpu吞吐率下降。
作用二:在多线程中,每一个线程都需要维护session,轻易完成对线程独享资源的操作。
& & &Threadlocal是什么?在堆内存中,每个线程对应一块工作内存,threadlocal就是工作内存的一小块内存。
& & &Threadlocal有什么用?threadlocal用于存取线程独享数据,提高访问效率。
三、ThreadLocal源码简要总结?
那有同学可能还是有点云里雾里,感觉还是没有吃透?那线程内部如何去保证线程独享数据呢?
在这里,我只做简要总结,若有兴趣,可参考文章尾部的文章链接。重点看get、set方法。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
createMap(t, value);
一个线程对应一个ThreadLocalMap ,可以存储多个ThreadLocal对象。
ThreadLocal对象作为key、独享数据作为value。
ThreadLocalMap可参考HashMap,在ThreadMap里面存在Entry数组也就是一个Entry一个键值对。
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.
return setInitialValue();
一个线程对应一个ThreadLocalMap,get()就是当前线程获取自己的ThreadLocalMap。
线程根据使用那一小块的threadlocal,根据ThreadLocal对象作为key,去获取存储于ThreadLocalMap中的值。
& & &回顾一下,我们在单线程中如何使用HashMap的?hashMap根据数组+链表来实现HashMap,一个key对应一个value。那么,我们抽象一下,Threadlocal也相当于在多线程中的一种HashMap用法,相当于对ThradLocal的操作也就如单线程操作一样。
& & &总之,ThreadLocal就是堆内存的一块小内存,它用ThreadLocalMap维护ThreadLocal对象作为key,独享数据作为value的东西。
四、ThreadLocal为什么会导致内存泄漏?
synchronized是用时间换空间、ThreadLocal是用空间换时间,为什么这么说?
因为synchronized操作数据,只需要在主存存一个变量即可,就阻塞等共享变量,而ThreadLocal是每个线程都创建一块小的堆工作内存。显然,印证了上面的说法。
一个线程对应一块工作内存,线程可以存储多个ThreadLocal。那么假设,开启1万个线程,创建1万个ThreadLocal,也就是每个线程维护1万个ThreadLocal小内存空间,而且当线程执行结束以后,假设这些ThreadLocal里的Entry还不会被回收,那么将很容易导致堆内存溢出。
怎么办?难道JVM就没有提供什么解决方案吗?
ThreadLocal当然有想到,所以他们把ThreadLocal里的Entry设置为弱引用,当垃圾回收的时候,回收ThreadLocal。
什么是弱引用?
Key使用强引用:也就是上述说的情况,引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为强引用并没有被回收,如果不手动回收的话,ThreadLocal将不会回收那么将导致内存泄漏。
Key使用弱引用:引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为弱引用,如果内存回收,那么将ThreadLocalMap的Key将会被回收,ThreadLocal也将被回收。value在ThreadLocalMap调用get、set、remove的时候就会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
那按你这么说,既然JVM有保障了,还有什么内存泄漏可言?
ThreadLocalMap使用ThreadLocal对象作为弱引用,当垃圾回收的时候,ThreadLocalMap中Key将会被回收,也就是将Key设置为null的Entry。如果线程迟迟无法结束,也就是ThreadLocal对象将一直不会回收,回顾到上面存在很多线程+TheradLocal,那么也将导致内存泄漏。
其实,在ThreadLocal中,当调用remove、get、set方法的时候,会清除为null的弱引用,也就是回收ThreadLocal。
JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
JVM利用调用remove、get、set方法的时候,回收弱引用。
当ThreadLocal存储很多Key为null的Entry的时候,而不再去调用remove、get、set方法,那么将导致内存泄漏。
当使用static ThreadLocal的时候,延长ThreadLocal的生命周期,那也可能导致内存泄漏。因为,static变量在类未加载的时候,它就已经加载,当线程结束的时候,static变量不一定会回收。那么,比起普通成员变量使用的时候才加载,static的生命周期加长将更容易导致内存泄漏危机。
&五、版权声明
  作者:邱勇Aaron
  出处:
  您的支持是对博主深入思考总结的最大鼓励。
  本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,尊重作者的劳动成果。
  参考:马士兵并发编程、并发编程实践
     ThreadLocal源码分析:
     ThradLocal内存分析实例:
     ThreadLoal导致内存泄漏:
阅读(...) 评论()

我要回帖

更多关于 threadlocalmap使用 的文章

 

随机推荐