欢迎赐教博客地址(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. Mysql 别名

    一.列别名 1.要给列添加别名,可以使用AS关键词后跟别名,例:SELECT [column_1 | expression] AS descriptive_name FROM table_name; ...

  2. CF1101C Division and Union 线段相交问题

    #include<iostream> #include<cstdio> #include<algorithm> #include<cstdlib> #i ...

  3. kuangbin专题十六 KMP&&扩展KMP POJ2406 Power Strings

    Given two strings a and b we define a*b to be their concatenation. For example, if a = "abc&quo ...

  4. PHP命名空间 namespace 及导入 use 的用法

    命名空间一个最明确的目的就是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误.这种情况下只要避免命名重复就可以解决,最常见的一种做法是约定一个前缀. 在PHP中,出现 ...

  5. 下载GitHub仓库的某个子文件夹

    http://downgit.zhoudaxiaa.com/#/home

  6. Qt 学习之路 2(6):Qt 模块简介

    Home / Qt 学习之路 2 / Qt 学习之路 2(6):Qt 模块简介  豆子  2012年8月26日  Qt 学习之路 2  20条评论 Qt 5 与 Qt 4 最大的一个区别之一是底层架构 ...

  7. html td 限制 高度 和 宽度

    td 要设置成 display : block td 里面的span 自动换行.. <td style="max-width: 150px;overflow-y:scroll;disp ...

  8. php 安装扩展库

    liunx系统 1. /usr/local/php/bin/php-config php 配置文件位置 [ php-config是一个脚本文件,用于获取所安装的php配置的信息 ] 在编译扩展时,如果 ...

  9. centos7初始优化

    第1章 优化 1.1 修改yum源 epel源 curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Cen ...

  10. day 012 生成器 与 列表推导式

    生成器的本质就是迭代器,写法和迭代器不一样,用法一样. 获取方法: 1.通过生成器函数 2.通过各种推导式来实现生成器 3.通过数据的转换也可以获取生成器 例如: 更改return 为 yield 即 ...