ThreadLocal 定义,以及是否可能引起的内存泄露(threadlocalMap的Key是弱引用,用线程池有可能泄露)
- JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
- JVM利用调用remove、get、set方法的时候,回收弱引用。
- 当ThreadLocal存储很多Key为null的Entry的时候,而不再去调用remove、get、set方法,那么将导致内存泄漏。
- 当使用static ThreadLocal的时候,延长ThreadLocal的生命周期,那也可能导致内存泄漏。因为,static变量在类未加载的时候,它就已经加载,当线程结束的时候,static变量不一定会回收。那么,比起普通成员变量使用的时候才加载,static的生命周期加长将更容易导致内存泄漏危机。http://www.importnew.com/22039.html
那么如何有效的避免呢?
事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!
threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法.
在threadlocal的生命周期中,都存在这些引用. 看下图: 实线代表强引用,虚线代表弱引用.

每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.
所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。
PS.Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。
应用场景
最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。如
private static ThreadLocal < Connection > connectionHolder = new ThreadLocal < Connection > () {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
一、目录
二、ThreadLocal是什么?有什么用?
/**
* 回顾synchronized在多线程共享线程的问题
* @author qiuyongAaron
*/
public class ThreadLocalOne {
volatile Person person=new Person(); public synchronized String setAndGet(String name){
//System.out.print(Thread.currentThread().getName()+":");
person.name=name;
//模拟网络延迟
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return person.name;
} 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=name;
} public Person(){}
} 运行结果:
无synchronized:
t1:tony
t2:tony 有synchronized:
t1:arron
t2:tony
- 无synchronized的时候,因为非原子操作,显然不是预想结果,可参考我关于synchronized的讨论。
- 现在,我们的需求是:每个线程独立的设置获取person信息,不被线程打扰。
- 因为,person是共享数据,用同步互斥锁synchronized,当一个线程访问共享数据的时候,其他线程堵塞,不再多余赘述。
/**
* 谈谈ThreadLocal的作用
* @author qiuyongAaron
*/
public class ThreadLocalThree {
ThreadLocal<Person> threadLocal=new ThreadLocal<Person>();
public String setAndGet(String name){
threadLocal.set(new Person(name));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return threadLocal.get().name;
} 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();
}
}
运行结果:
t1:arron
t2:tony


- ThreadLocal被称为线程局部变量,说白了,他就是线程工作内存的一小块内存,用于存储数据。
- 那么,ThreadLocal.set()、ThreadLocal.get()方法,就相当于把数据存储于线程本地,取也是在本地内存读取。就不会像synchronized需要频繁的修改主内存的数据,再把数据复制到工作内存,也大大提高访问效率。
- 回到最开始的举例,也就等价于mabatis、hibernate为什么要使用threadlocal来存储session?
- 作用一:因为线程间的数据交互是通过工作内存与主存的频繁读写完成通信,然而存储于线程本地内存,提高访问效率,避免线程阻塞造成cpu吞吐率下降。
- 作用二:在多线程中,每一个线程都需要维护session,轻易完成对线程独享资源的操作。
三、ThreadLocal源码简要总结?
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
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.value;
return result;
}
}
return setInitialValue();
}
- 一个线程对应一个ThreadLocalMap,get()就是当前线程获取自己的ThreadLocalMap。
- 线程根据使用那一小块的threadlocal,根据ThreadLocal对象作为key,去获取存储于ThreadLocalMap中的值。
四、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的时候会被清除。
ThreadLocal提供的方法

对于ThreadLocal而言,常用的方法,就是get/set/initialValue方法。
我们先来看一个例子

运行结果

很显然,在这里,并没有通过ThreadLocal达到线程隔离的机制,可是ThreadLocal不是保证线程安全的么?这是什么鬼?
虽然,ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象呢?这个时候ThreadLocal就失效了。仔细观察下图中的代码,你会发现,threadLocal在初始化时返回的都是同一个对象a!
看一看ThreadLocal源码
我们直接看最常用的set操作:



你会看到,set需要首先获得当前线程对象Thread;
然后取出当前线程对象的成员变量ThreadLocalMap;
如果ThreadLocalMap存在,那么进行KEY/VALUE设置,KEY就是ThreadLocal;
如果ThreadLocalMap没有,那么创建一个;
说白了,当前线程中存在一个Map变量,KEY是ThreadLocal,VALUE是你设置的值。

这里其实揭示了ThreadLocalMap里面的数据存储结构,从上面的代码来看,ThreadLocalMap中存放的就是Entry,Entry的KEY就是ThreadLocal,VALUE就是值。
ThreadLocalMap.Entry:

在JAVA里面,存在强引用、弱引用、软引用、虚引用。这里主要谈一下强引用和弱引用。
强引用,就不必说了,类似于:
A a = new A();
B b = new B();
考虑这样的情况:
C c = new C(b);
b = null;
考虑下GC的情况。要知道b被置为null,那么是否意味着一段时间后GC工作可以回收b所分配的内存空间呢?答案是否定的,因为即便b被置为null,但是c仍然持有对b的引用,而且还是强引用,所以GC不会回收b原先所分配的空间!既不能回收利用,又不能使用,这就造成了内存泄露。
那么如何处理呢?
可以c = null;也可以使用弱引用!(WeakReference w = new WeakReference(b);)
分析到这里,我们可以得到:

这里我们思考一个问题:ThreadLocal使用到了弱引用,是否意味着不会存在内存泄露呢?
首先来说,如果把ThreadLocal置为null,那么意味着Heap中的ThreadLocal实例不在有强引用指向,只有弱引用存在,因此GC是可以回收这部分空间的,也就是key是可以回收的。但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。
因此,只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间内不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,比如使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。
那么如何有效的避免呢?
事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!
参考:并发编程(四):ThreadLocal从源码分析总结到内存泄漏
ThreadLocal 定义,以及是否可能引起的内存泄露(threadlocalMap的Key是弱引用,用线程池有可能泄露)的更多相关文章
- 并发编程(四):ThreadLocal从源码分析总结到内存泄漏
一.目录 1.ThreadLocal是什么?有什么用? 2.ThreadLocal源码简要总结? 3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLoc ...
- 分析ThreadLocal的弱引用与内存泄漏问题
目录 一.介绍 二.问题提出 2.1内存原理图 2.2几个问题 三.回答问题 3.1为什么会出现内存泄漏 3.2若Entry使用弱引用 3.3弱引用配合自动回收 四.总结 一.介绍 之前使用Threa ...
- ThreadLocal源码分析以及why导致内存泄露
1 ThreadLocal? This class provides thread-local variables. These variables differ from their normal ...
- ThreadLocal 遇上线程池的问题及解决办法
ThreadLocal 称为线程本地存储,它为每一个使用它的线程提供一个其值(value)的副本.可以将 ThreadLocal<T> 理解成 Map<Thread, T>,即 ...
- ThreadLocal遇到线程池时, 各线程间的数据会互相干扰, 串来串去
最近遇到一个比较隐蔽而又简单地问题,在使用ThreadLocal时发现出现多个线程中值串来串去,排查一番,确定问题为线程池的问题,线程池中的线程是会重复利用的,而ThreadLocal是用线程来做Ke ...
- ThreadLocal定义、使用案例及源码分析
原文连接:(http://www.studyshare.cn/blog/details/1165/0 ) 一.ThreadLocal定义 jdk官方文档定义是:该类提供线程局部变量. 这些变量与其正常 ...
- 2017.9.2Java中的自定义类型的定义及使用&&自定义类的内存图
今日内容介绍 1.自定义类型的定义及使用 2.自定义类的内存图 3.ArrayList集合的基本功能 4.随机点名器案例及库存案例代码优化 01引用数据类型_类 * A: 数据类型 * a: java ...
- 证明:ThreadLocal的get,set方法无法防止内存泄漏
先给出结论:get,set两个方法都不能完全防止内存泄漏,还是每次用完ThreadLocal都勤奋的remove一下靠谱. 前言: 看到有的博客说在把ThreadLocal的所有强引用置空前,调用 ...
- JAVA多线程-内存模型、三大特性、线程池
一.线程的三大特性 原子性.可见性.有序性 1)原子性,即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行.原子性其实就是保证数据一致.线程安全一部分. 2)可见性,即 ...
随机推荐
- 网络编程的演进——从Apache到Nginx
Apache 1.Apache HTTP服务器是 Robert McCool 在1995年写成,并在1999年开始在Apache软件基金会的 框架下进行开发. 由于Apache HTTP服务器是基金会 ...
- EF6.0 code first感触
随着EF技术的更新现在已经到了EF7.0时代,追随着技术的大潮去不断更新迭代自己,让自己知道自己还没有被技术抛弃. 今天看了下EF 6.0 codefist技术,简单,对于传统的DAL层省去了大量的人 ...
- nginx遇到的配置问题
配置的例子如下: server { listen ; server_name localhost; #kzjtrans-web location /kzjtrans-web/ { proxy_pass ...
- 将jira添加至开机自启动
东北证券网金部jira项目管理系统,经常莫名挂掉,于是乎将jira服务加入开机自启动. jira.sh脚本代码如下: #!/bin/sh # chkconfig: # description:jira ...
- WebSocket抓包分析
转载自:https://www.cnblogs.com/songwenjie/p/8575579.html Chrome控制台 (1)F12进入控制台,点击Network,选中ws栏,注意选中Filt ...
- 2019 年软件开发人员必学的编程语言 Top 3
AI 前线导读:这篇文章将探讨编程语言世界的现在和未来,这些语言让新一代软件开发者成为这个数字世界的关键参与者,他们让这个世界变得更健壮.连接更加紧密和更有意义.开发者要想在 2019 年脱颖而出,这 ...
- Docker部署Golang
1. 安装docker 2. mkdir myDocker 3. cd myDocker && touch Dockerfile 4. Dockerfile写入 # 将golang ...
- mongoose和mongodb的几篇文章 (ObjectId,ref)
http://mongoosejs.com/docs/populate.html http://stackoverflow.com/questions/6578178/node-js-mongoose ...
- 团队冲刺--six
昨天: 司宇航:合并版块,但部分有缺陷. 马佳慧:研究css. 王金萱:写注册界面. 季方: 研究爬虫,精确的处理数据. 今天: 司宇航:测试功能版块,优化功能版块. 马佳慧:优化界面 . 王金萱: ...
- Task 6.3 冲刺Two之站立会议2
今天主要将聊天的主界面加以改善,添加了用户登陆后的提示,实现了好友的增删和查询以及自己的账号的个人信息,也可以使用户实现对自己的头像以及个性签名.个人信息等的管理.