java并发编程(九)ThreadLocal & InheritableThreadLocal
参考文档:
https://blog.csdn.net/u012834750/article/details/71646700
threadlocal内存泄漏:http://www.importnew.com/22039.html
什么是ThreadLocal
首先明确一个概念,那就是ThreadLocal并不是用来并发控制访问一个共同对象,而是为了给每个线程分配一个只属于该线程的变量,顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突,实现线程间的数据隔离。从线程的角度看,就好像每一个线程都完全拥有该变量
应用场景:
数据库连接池,session
一个简单栗子
public class ThreadLocalTest implements Runnable {
static ThreadLocal<String> tl = new ThreadLocal<String>(); public static void main(String[] args) {
for(int i=;i<;i++){
Thread t = new Thread(new ThreadLocalTest(), "t"+i);
t.start();
}
} @Override
public void run() {
tl.set(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+" == tname :"+tl.get());
}
}
输出:
t0 == tname :t0
t1 == tname :t1
t2 == tname :t2
t3 == tname :t3
t4 == tname :t4
源码分析
ThreadLocal持有一个静态类ThreadLocalMap 。ThreadLocalMap 维护了一个entry数组
static class ThreadLocalMap {
private Entry[] table;
}
Entry
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
entry相当于一个键值对,key是ThreadLocal,value就是当前线程变量的值。其中ThreadLocal是弱引用
也就是说ThreadLocal维护了一个集合。这个集合包含所有线程与之对应的值
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
其中Thread类持有属性ThreadLocal的静态类ThreadLocalMap
public class Thread implements Runnable{
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
创建localmap其实是初始化thread的localmap属性
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);//删除map里key为null的entry
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
get()
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
InheritableThreadLocal(可继承父线程的localmap)
InheritableThreadLocal定义如下。重写了getMap,createMap两个方法。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
/**
* Computes the child's initial value for this inheritable thread-local
* variable as a function of the parent's value at the time the child
* thread is created. This method is called from within the parent
* thread before the child is started.
* <p>
* This method merely returns its input argument, and should be overridden
* if a different behavior is desired.
*
* @param parentValue the parent thread's value
* @return the child thread's initial value
*/
protected T childValue(T parentValue) {
return parentValue;
} /**
* Get the map associated with a ThreadLocal.
*
* @param t the current thread
*/
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
} /**
* Create the map associated with a ThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the table.
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
之所以重写这两个方法。是因为InheritableThreadLocal唯一的不同在于操作thread的local属性不一样。
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null; /*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}
Thread初始化时,将父类localmap传递给子类
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) { if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
ThreadLocal为什么会内存泄漏
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
但是这些被动的预防措施并不能保证不会内存泄漏:
使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏(参考ThreadLocal 内存泄露的实例分析)
分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。
为什么使用弱引用
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
我们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key
下面我们分两种情况讨论:
key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏
key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用
不想看了 留一个问题 :什么时候算 持有entry key的强引用 ?
java并发编程(九)ThreadLocal & InheritableThreadLocal的更多相关文章
- Java并发编程:ThreadLocal
Java并发编程:深入剖析ThreadLocal Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用 ...
- Java并发编程--理解ThreadLocal
另一篇博文:Hibernet中的ThreadLocal使用 http://www.cnblogs.com/gnivor/p/4440776.html 本文参考:http://blog.csdn.net ...
- java并发编程学习: ThreadLocal使用及原理
多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么ThreadLocal可以很方便的帮你做到这一点. 先来看一下示例: package yjmyz ...
- Java并发编程:ThreadLocal的使用以及实现原理解析
前言 前面的文章里,我们学习了有关锁的使用,锁的机制是保证同一时刻只能有一个线程访问临界区的资源,也就是通过控制资源的手段来保证线程安全,这固然是一种有效的手段,但程序的运行效率也因此大大降低.那么, ...
- Java并发编程 (九) 线程调度-线程池
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 声明:实际上,在开发中并不会普遍的使用Thread,因为它具有一些弊端,对并发性能的影响比较大,如下: ...
- Java并发编程:深入剖析ThreadLocal(转载)
Java并发编程:深入剖析ThreadLocal(转载) 原文链接:Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadL ...
- (转)Java并发编程:深入剖析ThreadLocal
Java并发编程:深入剖析ThreadLoca Java并发编程:深入剖析ThreadLocal 说下自己的理解:使用ThreadLocal能够实现空间换时间,重在理解ThreadLocal是如何复制 ...
- [转载]Java并发编程:深入剖析ThreadLocal
原文地址:http://www.cnblogs.com/dolphin0520/p/3920407.html 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨 ...
- 【转载】 Java并发编程:深入剖析ThreadLocal
原文链接:http://www.cnblogs.com/dolphin0520/p/3920407.html感谢作者的辛苦总结! Java并发编程:深入剖析ThreadLocal 想必很多朋友对Thr ...
- Java并发编程原理与实战二十五:ThreadLocal线程局部变量的使用和原理
1.什么是ThreadLocal ThreadLocal顾名思义是线程局部变量.这种变量和普通的变量不同,这种变量在每个线程中通过get和set方法访问, 每个线程有自己独立的变量副本.线程局部变量不 ...
随机推荐
- 『Norma 分治』
Norma Description Input Format 第1行,一个整数N: 第2~n+1行,每行一个整数表示序列a. Output Format 输出答案对10^9取模后的结果. Sample ...
- Redis(四)Pub/Sub
发布与订阅 Pub/Sub模式应该非常熟悉,在现实应用中被广泛的使用.如:微博中关注某个号,这个号有发新博时,关注的都会收到:github上watch了某个项目,当有issue时,就会发邮件. Red ...
- 单片机成长之路(stm8基础篇)- 025 stm8 时钟切换
stm8 时钟切换; /************************************ 时钟设置 ************************************/ // 时钟 0: ...
- pom文件语法无误却报红叉(Unknown error)的可能原因
本文链接:https://blog.csdn.net/Little_Stars/article/details/94553090可能原因及解决办法: >> 缓存问题,只要 “Maven - ...
- C# vb .NET识别读取QR二维码
二维码比条形码具有更多优势,有些场合使用二维码比较多,比如支付.那么如何在C#,.Net平台代码里读取二维码呢?答案是使用SharpBarcode! SharpBarcode是C#快速高效.准确的条形 ...
- 字节输出流FileOutputStream
#字节流 字节输出流FileOutputStream 创建输出流对象 OutputStream 流对象是一个抽象类,不能实例化.所以,我们要找一个具体的子类 :FileOutputStream. 查看 ...
- 2019-09-16 curl简单操作
1.get请求 (使用file_get_contents()函数也可以实现get请求) //http_build_query() 构造一个url字符串 function http_get($url) ...
- 2019-08-26 linux
1.question:什么是linux? answer:Linux由林纳斯·托瓦兹开发的一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和Unix的多用户.多任务.支持多线程和多CPU ...
- HTML实现百度分享插件分享网页
一.HTML代码如下: <div class="bdsharebuttonbox"> <a href="#" class="bds_ ...
- Jmeter测试技巧
最近在用Jmeter做接口测试,使用中整理了一些组件的使用技巧. 一. 用户定义的变量 都是全局变量,无论是否在某个线程组或请求内,都是采用最新赋值的内容 二. 固定定时器 在单个请求内是让本请求线程 ...