也谈ThreadLocal
欢迎赐教博客地址(http://www.cnblogs.com/shizhongtao/p/5358411.html)
对于ThreadLocal使用,网上一堆一堆的。什么内存泄露,什么线程不安全。这里记下自己对其的理解。以备不时之需。
关于ThreadLocal
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类
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服务器
也谈ThreadLocal的更多相关文章
- 浅谈 ThreadLocal
有时,你希望将每个线程数据(如用户ID)与线程关联起来.尽管可以使用局部变量来完成此任务,但只能在本地变量存在时才这样做.也可以使用一个实例属性来保存这些数据,但是这样就必须处理线程同步问题.幸运的是 ...
- 浅谈ThreadLocal模式
一.前言: ThreadLocal模式,严格意义上不是一种设计模式,而是java中解决多线程数据共享问题的一个方案.ThreadLocal类是java JDK中提供的一个类,用来解决线程安全问题,并不 ...
- AsyncLocal 与 ThreadLocal ThreadStatic特性简介
AsyncLocal 与 ThreadLocal [.NET深呼吸]基于异步上下文的本地变量(AsyncLocal) https://www.cnblogs.com/tcjiaan/p/5007737 ...
- 手撕面试题ThreadLocal!!!
说明 面试官:讲讲你对ThreadLocal的一些理解. 那么我们该怎么回答呢????你也可以思考下,下面看看零度的思考: ThreadLocal用在什么地方? ThreadLocal一些细节! Th ...
- 对ThreadLocal的一些理解
ThreadLocal也是在面试过程中经常被问到的,本文主要从以下三个方面来谈对ThreadLocal的一些理解: ThreadLocal用在什么地方 ThreadLocal一些细节 ThreadLo ...
- 【Java】说说你对ThreadLocal的理解
思路: 0.ThreadLocal是什么?有什么用? 1.ThreadLocal用在什么地方? 2.ThreadLocal的一些细节 3.ThreadLocal的最佳实践 一.ThreadLocal用 ...
- 百战程序员——Spring框架
什么是容器,我们学过了哪些容器,Spring与我们之前学习的容器有哪些异同点? 容器可以管理对象的生命周期.对象与对象之间的依赖关系,您可以使用一个配置文件(通常是XML),在上面定义好对象的名称.如 ...
- Java 面试知识点【背诵版 240题 约7w字】
-- 转载自牛客网 是瑶瑶公主吖 Java 基础 40 语言特性 12 Q1:Java 语言的优点? ① 平台无关性,摆脱硬件束缚,"一次编写,到处运行". ② 相对安全的内存管理 ...
- 浅谈Java引用和Threadlocal的那些事
这篇文章主要介绍了Java引用和Threadlocal的那些事,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 1 背景 某一天在某一个群里面的某个群友突然提出了一个问 ...
随机推荐
- linux 虚拟环境问题
1.python环境 python2和python3命令用来区分python版本 pip2和pip3命令用来区分pip,你的包到底安装在哪里pip3 install xxx sudo apt inst ...
- Qt 学习之路 2(9):资源文件
Qt 学习之路 2(9):资源文件 豆子 2012年8月31日 Qt 学习之路 2 62条评论 上一章节中我们介绍了如何使用QAction添加动作.其中,我们使用QIcon加载了一张 png ...
- appium环境配置和一个例子
最近觉得appium挺火的,看了一些资料,本来想使用npm在线安装,遇见各种问题,先简单说一下: 在cmd窗口中使用命令:npm install -g appium安装,报无python的error, ...
- POJ1051 P,MTHBGWB
题目来源:http://poj.org/problem?id=1051 题目大意: Morse密码里每个字母用长度不定的点和线来表示,一条信息中字母的编码之间用空隙隔开.下表为Morse密码的编码表: ...
- SwiftMailer 发送邮件时 提示fsockopen() 被禁用
站点转移空间,发送邮件的SwiftMailer 类提示错误如下: Warning: fsockopen() has been disabled for security reasons in D:\1 ...
- codeforces 985C Liebig's Barrels(贪心)
题目 题意: 有n * k块木板,每个木桶由k木板组成,每个木桶的容量定义为它最短的那块木板的长度. 任意两个木桶的容量v1,v2,满足|v1-v2| <= d. 问n个木桶容量的最大的和为多少 ...
- 毕业设计 python opencv实现车牌识别 预处理
主要代码参考https://blog.csdn.net/wzh191920/article/details/79589506 GitHub:https://github.com/yinghualuow ...
- 协议 + socket import 和 form xx import *的区别 028
一 . 网络通信协议(了解) 1 . osi 七层协议 (最好记住 面试会问) 应表会传网数物(应用层 表示层 会话层 传输层 网络层 数据链路层 物理层) 2 .tcp/ip五层 或 tcp/ip四 ...
- 服务器报nginx: [warn] conflicting server name "blog.xueyi.com" on 0.0.0.0:80, ignored
nginx: [warn] conflicting server name "blog.xueyi.com" on 0.0.0.0:80, ignored 修改nginx配置参数后 ...
- 原 tomcat的server.xml配置文件中三个端口的作用
以Tomcat7.0为例, 在安装目录下. conf/server.xml 中可以配置三个端口号, 如果使用多个tomcat 是需要配置这三个. 该Connector 用于监听请求. protocol ...