欢迎赐教博客地址(http://www.cnblogs.com/shizhongtao/p/5358411.html)

对于ThreadLocal使用,网上一堆一堆的。什么内存泄露,什么线程不安全。这里记下自己对其的理解。以备不时之需。

关于ThreadLocal

ThreadLocal类,在jdk中这样描述:
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).
其实说白了就是保持本线程变量在不同阶段的共享,用我曾看到的一句话描述就是:ThreadLocal模式主要用于解决同一线程在不同开发层次中数据共享问题,
而不是用于不同开发层次数据传递问题,比如spring的数据库连接传递性的实现。我的理解就是,threadlocal里面的数据本身就是每个线程一份,也就是线程私有的,
没有用它来做线程共享的概念(不对的话,请赐教)。
另外,基于上面一点,既然他是一个线程共享的私有变量,并且为了避免引起不必要的问题,你可以把他定义为,final 并且 static的变量
final static ThreadLocal<T> local;
另外,关于threadLocal使用问题,我觉得一般是不正确的编码引起的。比如:
  • 为了方便测试,我定义一个ThreadLocal类

    public static class ThreadMyLocal<T> extends ThreadLocal<T> {
    
            @Override
    protected void finalize() throws Throwable {
    System.out.println("threadLocal gc:");
    super.finalize();
    } }
  • 定义处理类,引入全局的ThreadLocal对象
    public static class Handler{
    static ThreadMyLocal<Person> local; private Person getPerson() {
    if (local == null) {
    local = new ThreadMyLocal<>();
    }
    if(local.get()==null){
    Person person = new Person();
    person.setAge(100);
    local.set(person);
    }
    return local.get();
    }
    public void printSome(){ Person person = getPerson();
    System.out.println(person.getAge()+":Person对象age属性");
    } @Override
    protected void finalize() throws Throwable {
    System.out.println("Handler GC");
    super.finalize();
    } }
  • 测试用的变量 person对象(ThreadLocal存放的值)
    public static class Person {
    
            private int age;
    
            public int getAge() {
    return age;
    }
    public void setAge(int age) {
    this.age = age;
    } @Override
    protected void finalize() {
    System.out.println("Person gc");
    } }

测试1,我们进行第一次测试

@Test
public void readExcelTest() throws InterruptedException { Handler h=new Handler();
h.printSome();
h=null;
Thread.sleep(1000);
System.out.println("gc1");
System.gc(); Handler h2=new Handler();
h2.printSome(); Thread.sleep(1000);
System.out.println("gc2");
System.gc();
}

打印结果是:

100:Person对象age属性
gc1
100:Person对象age属性
Handler GC
gc2

我们从打印结果看,只能看到handler被销毁回收,原因很简单,因为这里面并没有启动新的线程,而是在主线程中来操作Threadlocal对象,他们共用一个Pserson对象,所以Person不该被销毁。

测试2 第二次测试

@Test
public void readExcelTest() throws InterruptedException { Handler h=new Handler();
h.printSome();
//注意此处
h.local=null;
Thread.sleep(1000);
System.out.println("gc1");
System.gc();
h=new Handler();
h.printSome(); Thread.sleep(1000);
System.out.println("gc2");
System.gc(); }

打印结果是:

100:Person对象age属性
gc1
100:Person对象age属性
threadLocal gc:
gc2
Handler GC

可以发现,设置   h.local=null;时候。ThreadLocal对象在设置为null时候,被回收了,而Person对象并没有被回收。当把threadlocal实例( h.local=null;)以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收.

但是,其中的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收

这里推荐一篇博客(深入JDK源码之ThreadLocal类),它里面有这样一段介绍:

在java api中:ThreadLocal有这样的观点(转载)
ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。
ThreadLocal 在类中通常定义为静态类变量。
每个线程有自己的一个ThreadLocal,它是变量的一个‘拷贝’,修改它不影响其他线程。
对ThreadLocal的修改,其实是一种对线程资源的修改,可以说这是一种空间换取时间的设计。
ThreadLocal内存泄漏 很多人认为:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没 有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法。
说的也比较正确,当value不再使用的时候,调用remove的确是很好的做法.但内存泄露一说却不正确. 这是threadlocal的设计的不得已而为之的问题. 首先,让我们看看在threadlocal的生命周期中,都存在哪些引用吧.
看下图: 实线代表强引用,虚线代表弱引用。 每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal.
当把threadlocal实例tl置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用.
只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。通过源码看下此处的实现,如下:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // 将当前threadLocal实例作为key
else
createMap(t, value);
} private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not. 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); // 构造key-value实例
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
} static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal k, Object v) {
super(k); // 构造key弱引用
value = v;
}
} public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
} ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
从中可以看出,弱引用只存在于key上,所以key会被回收. 而value还存在着强引用.只有thead退出以后,value的强引用链条才会断掉。一旦某个ThreadLocal对象没有强引用了,
它在所有线程 内部的ThreadLocalMap中的key都将被GC掉(此时value还未回收),在map后续的get/set中会探测到key被回收的 entry,将其 value 设置为 null 以帮助
GC,因此 value 在 key 被 GC 后可能还会存活一段时间,但最终也会被回收。这个过程和java.util.WeakHashMap的实现几乎是一样的。
因此ThreadLocal本身是没有内存泄露问题的,通常由它引发的内存泄露问题都是线程只 put 而忘了 remove 导致的,从上面分析可知,即使线程退出了,只要 ThreadLocal
还有强引用,该线程曾经 put 过的东西是不会被回收掉的。

上面说的比较清楚了,如果你想value值被回收,只有当当前线程退出。

测试4,为了说明以上内容

@Test
public void readExcelTest2() throws InterruptedException { new Thread(new Runnable() { @Override
public void run() {
Handler h=new Handler();
h.printSome();
//h.local=null;
}
});
System.out.println("gc1");
System.gc();
Thread.sleep(3000);
System.out.println("gc2");
System.gc(); }

打印结果是:

gc1
100:Person对象age属性
gc2
Person gc
Handler GC

从上面结果可以看出,线程退出Persong确实被回收了。

ThreadLocal之Web服务器

当你在javaEE项目中使用ThreadLocal时候就可能会出现内存问题,我们知道不管是tomcat还是webLogic本身都有一个线程池的概念,这样可以提高服务器的性能。简单来说就是当一个请求过来,容器就会去线程池中拿走一个线程去使用;请求
结束就会把这个线程放到线程池中等待使用。正是这个原因,使用的”ThreadLocal”保存的值,在下一次请求依然存在,而且值也不能完全确定(也可能两次同一个线程,也可能不是),这是其中一个问题;另外假如Threadlocal保存的值有5M大小,
在生产环境,我们把服务器线程池设置允许150或者更多,这样只是Threadlocal就会占用去差不多1G的内存空间,也可能就会出现OutOfMemoryError这样的错误。
 
正常情况下使用ThreadLocal不会造成内存溢出,在程序中也不要滥用Threadlocal,能用参数传递的就不要使用Threadlocal,还是前面那句话:ThreadLocal模式主要用于解决同一线程在不同开发层次中数据共享问题,而不是用于不同开发层次数据传递问题。

也谈ThreadLocal的更多相关文章

  1. 浅谈 ThreadLocal

    有时,你希望将每个线程数据(如用户ID)与线程关联起来.尽管可以使用局部变量来完成此任务,但只能在本地变量存在时才这样做.也可以使用一个实例属性来保存这些数据,但是这样就必须处理线程同步问题.幸运的是 ...

  2. 浅谈ThreadLocal模式

    一.前言: ThreadLocal模式,严格意义上不是一种设计模式,而是java中解决多线程数据共享问题的一个方案.ThreadLocal类是java JDK中提供的一个类,用来解决线程安全问题,并不 ...

  3. AsyncLocal 与 ThreadLocal ThreadStatic特性简介

    AsyncLocal 与 ThreadLocal [.NET深呼吸]基于异步上下文的本地变量(AsyncLocal) https://www.cnblogs.com/tcjiaan/p/5007737 ...

  4. 手撕面试题ThreadLocal!!!

    说明 面试官:讲讲你对ThreadLocal的一些理解. 那么我们该怎么回答呢????你也可以思考下,下面看看零度的思考: ThreadLocal用在什么地方? ThreadLocal一些细节! Th ...

  5. 对ThreadLocal的一些理解

    ThreadLocal也是在面试过程中经常被问到的,本文主要从以下三个方面来谈对ThreadLocal的一些理解: ThreadLocal用在什么地方 ThreadLocal一些细节 ThreadLo ...

  6. 【Java】说说你对ThreadLocal的理解

    思路: 0.ThreadLocal是什么?有什么用? 1.ThreadLocal用在什么地方? 2.ThreadLocal的一些细节 3.ThreadLocal的最佳实践 一.ThreadLocal用 ...

  7. 百战程序员——Spring框架

    什么是容器,我们学过了哪些容器,Spring与我们之前学习的容器有哪些异同点? 容器可以管理对象的生命周期.对象与对象之间的依赖关系,您可以使用一个配置文件(通常是XML),在上面定义好对象的名称.如 ...

  8. Java 面试知识点【背诵版 240题 约7w字】

    -- 转载自牛客网 是瑶瑶公主吖 Java 基础 40 语言特性 12 Q1:Java 语言的优点? ① 平台无关性,摆脱硬件束缚,"一次编写,到处运行". ② 相对安全的内存管理 ...

  9. 浅谈Java引用和Threadlocal的那些事

      这篇文章主要介绍了Java引用和Threadlocal的那些事,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 1 背景 某一天在某一个群里面的某个群友突然提出了一个问 ...

随机推荐

  1. Tarjan缩点+LCA【洛谷P2416】 泡芙

    P2416 泡芙 题目描述 火星猫经过一番努力终于到达了冥王星.他发现冥王星有 N 座城市,M 条无向边.火星猫准备出发去找冥王兔,他听说有若干泡芙掉落在一些边上,他准备采集一些去送给冥王兔.但是火星 ...

  2. linux heap堆分配

    heap堆分配在用户层面:malloc函数用于heap内存分配 void* malloc(size_t size); 进程的虚拟内存地址布局: 对用户来说,主要关注的空间是User Space.将Us ...

  3. kuangbin专题十六 KMP&&扩展KMP HDU3068 最长回文

    给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度. 回文就是正反读都是一样的字符串,如aba, abba等 Input输入有多组case,不超过120组,每组输入为 ...

  4. Gradle 引入本地定制 jar 包

    第 1 步:创建文件夹,拷贝 jar 包 在自己的 Gradle 项目里建立一个名为 “libs” (这个名字可以自己定义,不一定非要叫这个名字)的文件夹,把自己本地的 jar 包拷贝到这个文件夹中. ...

  5. Kibana6.x.x——源码发布

    从官方GitHub上克隆下来的源码自己如何发布? 我在Ubuntu系统中进行的开发,安装了yarn. 执行build命令为:$ yarn build 执行release命令为:$ yarn relea ...

  6. codeforces-473D Mahmoud and Ehab and another array construction task (素数筛法+贪心)

    题目传送门 题目大意:先提供一个数组,让你造一个数组,这个数组的要求是 1 各元素之间都互质  2  字典序大于等于原数组  3 每一个元素都大于2 思路: 1.两个数互质的意思就是没有公因子.所以每 ...

  7. BAPC 2014:Button Bashing(暴力+bfs)

    题意: 给出n,m,代表微波炉有n个按钮,要求达到总时间为m 然后给出n个数,代表n个按钮能增加的时间,问最少几步,能够使得按出的总时间大于等于要求的时间,并且相差最小 输出最小的步数与相差的最小值 ...

  8. Java8 流的使用示例

    foreach遍历处理 dataList.stream().forEach(index -> sb.append(dataList.get(index) + "',")); ...

  9. 【ACM】Knapsack without repetition - 01背包问题

    无界背包中的状态及状态方程已经不适用于01背包问题,那么我们来比较这两个问题的不同之处,无界背包问题中同一物品可以使用多次,而01背包问题中一个背包仅可使用一次,区别就在这里.我们将 K(ω)改为 K ...

  10. java——快排、冒泡、希尔、归并

    直接贴代码 快排: public class Test { private static void sort(int[] nums){ if(nums == null || nums.length = ...