JavaSe:ThreadLocal
JDK中有一个ThreadLocal类,使用很方便,但是却很容易出现问题。究其原因, 就是对ThreadLocal理解不到位。最近项目中,出现了内存泄漏的问题。其中就有同事在使用ThreadLocal时,没有用好。所以特写下此文。
- ThreadLocal的设计
- ThreadLocalMap、ThreadLocal说明
- 使用ThreadLocal后的内存模型
- 如何正确的使用ThreadLocal
- 错误的使用ThreadLocal会造成内存泄漏
ThreadLocal设计
ThreadLocal的类图:

·每个Thread都有一个自己的ThreadLocalMap,默认是null。当线程运行过程中,首次使用某个ThreadLocal对象时,会初始化这个ThreadLocalMap。
·ThreadLocalMap中包括一个Entry数组。我们在程序中是不能直接使用ThreadLocalMap的。在每次使用ThreadLocal时(set,get,remove),都会自动的使用ThreadLocalMap,进行一定的清理工作。参见方法expungeStaleEntry。
·Entry继承了WeakReference,Key是ThreadLocal对象,Value时目标对象。并且key是被Weak Reference的。所以如果一个ThreadLocal对象TL1被GC后,也就是key是null。
·每个ThreadLocal对象都有一个initValue。在继承ThreadLocal类时,可以对它的initialValue()进行重写。ThreadLocal对外提供了3个常用方法:get,set,remove。
此外,当线程被kill(通过interrupe,或者线程自然结束)时,ThreadLocalMap会被设置为null,那么Map中的各个变量也会被回收(如果不被其它变量引用的话)。
ThreadLocalMap、ThreadLocal说明
ThreadLocalMap
ThreadLocalMap中的expungeStaleEntry(int)方法的可能被调用的处理有:

通过这个图,不难看出这个方法在ThreadLocal的set,get,remove时都会被调用。

从该方法代码中,可以看出是先清理指定的Entry,再遍历,如果发现有Entry的key是null,清理之。Key =null,也就是ThreadLocal对象是null。所以当程序中,将ThreadLocal对象设置为null,在该线程继续执行时,如果执行另一个ThreadLocal时,就会触发该方法。就有可能清理掉key是null的那个ThreadLocal对应的值。
ThreadLocal方法说明:
set() 将目标对象作为当前线程的局部变量的值设置。也就是说,变量名是ThreadLocal对象,变量值是目标对象。
get(),从当前对象中取出ThreadLocal变量的值。如果map中不并在该ThreadLocal变量,就会自动调用initialValue方法。并将该值设置到map中。
remove(),从当前线程中移除ThreadLocal变量的值。此时key,value都不存在map中。
使用ThreadLocal后的内存模型
假设程序中指定2个ThreadLocal的变量TL1、TL2,并且他们都是static时。
private static ThreadLocal<ClassB> TL1 = new ThreadLocal<ClassB>(); private static ThreadLocal<ClassA> TL2 = new ThreadLocal<ClassA>();
在运行时有2个线程,每个线程中都设置了这两个线程局部变量TL1,TL2。那么内存模型将是这样的:

在线程Thread1执行过程中,如果执行顺序是这样的:
TL1.set(ObjB1); //将TL1作为变量名,OBjB1作为变量值设置到Thread1中。 TL1.get(); //从当前线程Thread1中取出变量TL1的值。它的值应该是OBjB1 TL2.set(ObjA1); //将TL2作为变量名,OBjA1作为变量值设置到Thread1中。 TL2.get(); //从当前线程Thread1中取出变量TL1的值。它的值应该是OBjA1
当执行remove时,应会将该值从ThreadLocalMap中移出。
如何正确使用ThreadLocal呢?
在说如何正确使用之前,先来看一个例子:
package com.fjn.jdk.thread_concurrent.threadlocal;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
public class ThreadLocalTest {
@Test
public void testUsingThreadPool() {
ExecutorService executor = Executors.newCachedThreadPool();
int workerThreadNumber =10;
CountDownLatch countDowner = new CountDownLatch(workerThreadNumber);
for (int i = 0; i < workerThreadNumber; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// clear interrupted flag
Thread.interrupted();
}
executor.submit(new Task(countDowner));
}
try {
countDowner.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
executor.shutdownNow();
}
}
class NamedThreadLocal<T> extends ThreadLocal<T> {
private String name;
public NamedThreadLocal(String name) {
this.name = name;
}
public String toString() {
return "The thread local [" + name + "] is " + this.get();
}
}
class BusService {
private CooperationService cooperateService = CooperationService.getService();
public void doService(String str) {
try {
cooperateService.recordStartTime();
Thread.sleep(500);
doSomething(str);
Thread.sleep(500);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} finally {
cooperateService.clear();
// 此时ThreadLocalMap中已不存在该变量。
}
// 正常情况下是不能要这一行代码的:
// 因为这样做又会重新在ThreadLocalMap中添加一个ThreadLocal变量,并设置值为initailValue
System.out.println(cooperateService.getStartTime());
}
private void doSomething(String str) {
System.out.println(" do something beging ...");
System.out.println("start time is : " + cooperateService.getStartTime());
System.out.println(str);
System.out.println(" do something end ...");
}
}
class CooperationService {
private static CooperationService ins = new CooperationService();
private static ThreadLocal<Long> threadLocal = new NamedThreadLocal<Long>("Test") {
protected Long initialValue() {
return -1l;
};
};
private CooperationService(){
}
public static CooperationService getService(){
return ins;
}
public void recordStartTime() {
long now = System.currentTimeMillis();
System.out.println("record start time : " + now);
threadLocal.set(now);
}
public Long getStartTime() {
return threadLocal.get();
}
public void clear() {
threadLocal.remove();
}
}
class Task implements Runnable {
private final CountDownLatch countDownLatch;
public Task(CountDownLatch countDowner) {
this.countDownLatch = countDowner;
}
@Override
public void run() {
BusService service = new BusService();
service.doService("hello");
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
上面的例子中,使用线程池执行一个Task。Task的内容是调用相关的业务服务,在业务处理过程中,会先记录下业务处理的开始时间,处理过程中可能会使用到这个开始时间,譬如说要将开始处理时间记录到DataBase中。在用完之后,线程要完成任务之前,调用remove将该变量从线程中移出。
在调用ThreadLocal方法get,set,remove时,正确的姿势应该是:
TL1.set(obj);
Try{
// . . .
TL1.get();
// . . .
}
Finally{
TL1.remove();
}
那么ThreaLocal变量该如何声明呢?
要分为下面几个使用场景了:
1)如果ThreadLocal可以完全在一个方法中使用,不会到另一个方法中使用该ThreadLocal对象。对于这种情况:ThreadLocal完全可以作为该方法的局部变量。例如:
Class Hello {
public void hello () {
// . . .
ThreadLocal<Long> tl = new ThreadLocal<Long>();
tl.set(obj);
// . . .
tl.remove();
}
}
但这种情况毕竟是少数,因为在这种情况下,完全可以不用ThreadLocal的。
2)如果ThreadLocal是在同一个类中的不同方法中使用,可以将ThreadLocal作为该类的字段。
public class Hello {
[static] ThreadLocal<Long> tl = new ThreadLocal<Long>();
public void method1(){
// . . .
tl1.set();
// . . .
method2();
}
private void method2(){
tl.get();
// . . .
tl.remove();
}
}
这种情况用的相对来说,会多一些。在这种情况下,通常建议的做法是将ThreadLocal声明为static的,但也不是必须的。
3)ThreadLocal要跨类使用,一种方案是将ThreadLocal设置为public static的。另一种方案是像上面的例子一样。
错误使用ThreadLocal造成内存泄漏
实际使用过程中,大家很少会去使用ThreadLocal.remove()方法。另外,还有人会用ThreadLocal来做cache,那就更不可能去使用remove方法了。
前面提到,当线程结束时,ThreadLocalMap也会被自动清理,ThreadLocalMap中的对象如果没有其它的引用,就会被GC掉,这样就不会发生内存泄漏了。
如果线程A没有结束,某个ThreadLocalA对象如果被设置为null,线程A再次执行时,如果还使用到其它的ThreadLocalB。ThreadLocalA关联到的对象也会从ThreadLocalMap中移出。这样也可以避免内存泄漏。
如果线程A没有结束,某个ThreadLocalA对象如果被设置为null,线程A再次使用时,如果是执行的其它的任务,没有使用到任何的ThreadLocal,那么就会造成内存泄漏。
在使用ThreadLocal时,理解它很重要,更要知道怎么用它。
JavaSe:ThreadLocal的更多相关文章
- Java多线程10:ThreadLocal的作用及使用
ThreadLocal的作用 从上一篇对于ThreadLocal的分析来看,可以得出结论:ThreadLocal不是用来解决共享对象的多线程访问问题的,通过ThreadLocal的set()方法设置到 ...
- Java多线程9:ThreadLocal源码剖析
ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...
- java并发编程(3):ThreadLocal
转载:http://www.cnblogs.com/dolphin0520/p/3920407.html 一. 对ThreadLocal的理解 ThreadLocal,很多地方叫做线程本地变量,也有地 ...
- 并发编程(四):ThreadLocal从源码分析总结到内存泄漏
一.目录 1.ThreadLocal是什么?有什么用? 2.ThreadLocal源码简要总结? 3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLoc ...
- 一个故事讲明白线程的私家领地:ThreadLocal
张大胖上午遇到了一个棘手的问题,他在一个AccountService中写了一段类似这样的代码: Context ctx = new Context(); ctx.setTrackerID(.....) ...
- java多线程18: ThreadLocal的作用
从上一篇对于ThreadLocal的分析来看,可以得出结论:ThreadLocal不是用来解决共享对象的多线程访问问题的,通过ThreadLocal的set()方法设置到线程的ThreadLocal. ...
- Java并发(4):ThreadLocal
一.对ThreadLocal的理解 ThreadLocal是java.lang包中的一个类,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多.可能很多朋友都知道ThreadLoca ...
- 正确理解ThreadLocal:ThreadLocal中的值并不一定是完全隔离的
首先再讨论题主的这个观点之前我们要明确一下ThreadLocal的用途是什么? ThreadLocal并不是用来解决共享对象的多线程访问问题. 看了许多有关ThreadLocal的博客,看完之后会给人 ...
- 多线程之:ThreadLocal
Java中ThreadLocal类可以使创建的变量只被同一个线程进行读和写操作,即使有多个线程同时执行同一段代码,并且这段代码中又有一个指向同一个ThreadLocal变量的引用,这些线程依然不能看到 ...
随机推荐
- ASP.net 内置对象
.net初学者,有错误欢迎指正.大家共同进步 Response 输出数据 Reponse对象和Request对象组成了一对发送,接受数据的对象. 发送信息:Reponse.Write("字符 ...
- linux下如何添加一个用户并且让用户获得root权限
1.添加用户,首先用adduser命令添加一个普通用户,命令如下: #adduser tommy //添加一个名为tommy的用户 #passwd tommy //修改密码 Changing pass ...
- js文章列表的树形结构输出
文章表设计成这样了 后端直接给了无任何处理的json数据,现在要前端实现树形结构的输出,其实后端处理更简单写,不过既然来了就码出来 var doclist = [{ "id": 1 ...
- [WPF]控件应用多个样式
最近在做WPF项目,公司没有专门的UI工程师,什么都要自己做.接触WPF已经有好几年了,自定义样式什么的也可以做一些.WPF在使用样式的时候一般都是 Style="{StaticResour ...
- 前端学HTTP之客户端识别和cookie
前面的话 Web服务器可能会同时与数千个不同的客户端进行对话.这些服务器通常要记录下它们在与谁交谈,而不会认为所有的请求都来自匿名的客户端.本文主要介绍客户端识别及cookie机制 HTTP首部 HT ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(52)-美化EasyUI皮肤和图标
系列目录 我很久以前就想更新系统的皮肤功能,Easyui 自带的皮肤已经无法满足客户的审美. 皮肤颜色来源于AdminLTE系统.我的颜色全部都这里取的.,所以一共取了11个颜色.1个皮肤=2个ban ...
- 微软Power BI技术文章与资源目录
下面是本博客原创的微软Power BI技术相关文章,对于部分转载文章和资源,会注明出处. 本博客将发布基于微软Power BI相关的基础入门文章,视频教程等资源,敬请关注. 个人建立的Power BI ...
- 原生JS封装Ajax插件(同域&&jsonp跨域)
抛出一个问题,其实所谓的熟悉原生JS,怎样的程度才是熟悉呢? 最近都在做原生JS熟悉的练习... 用原生Js封装了一个Ajax插件,引入一般的项目,传传数据,感觉还是可行的...简单说说思路,如有不正 ...
- 【NLP】基于统计学习方法角度谈谈CRF(四)
基于统计学习方法角度谈谈CRF 作者:白宁超 2016年8月2日13:59:46 [摘要]:条件随机场用于序列标注,数据分割等自然语言处理中,表现出很好的效果.在中文分词.中文人名识别和歧义消解等任务 ...
- Unicode转义(\uXXXX)的编码和解码
在涉及Web前端开发时, 有时会遇到\uXXXX格式表示的字符, 其中XXXX是16进制数字的字符串表示形式, 在js中这个叫Unicode转义字符, 和\n \r同属于转义字符. 在其他语言中也有类 ...