ThreadLocal 线程本地变量 及 源码分析
■ ThreadLocal 定义
- ThreadLocal通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题
- 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
- 在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本
- ThreadLocal 自我认识:可以把 "宾馆" 比作应用程序,每个房间为 "子线程",ThreadLocal 就是为每个 "房间" 提供服务的人员,各自不相互冲突并保持独立
- JDK1.5引入泛型后,ThreadLocal告别Object时代进入泛型时代
- 存储线程私有变量的一个容器
- ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性
■ ThreadLocal 数据结构
1. 类定义
public class ThreadLocal<T>
2. 重要的内部元素
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
3. 构造器
/** ThreadLocal只提供了一个空的默认构造器,够纯粹 **/
public ThreadLocal() {}
■ ThreadLocal 重要方法
1. set() 方法
/**
* Sets the current thread's copy of this thread-local variable to the specified value.
* Most subclasses will have no need to override this method,relying solely on the
* {@link #initialValue} method to set the values of thread-locals.
* 设置当前线程在当前ThreadLocal中的线程局部变量的值
* 其子类无须重写该方法,只要重写initialValue方法设置初始默认值即可
* @param value the value to be stored in the current thread's copy of
* this thread-local. 将当前值拷贝成当前线程的局部变量
*/
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程持有的Map
ThreadLocalMap map = getMap(t);
//createMap or set directly
if (map != null)
//注意 key为this,指的就是当前调用set方法的ThreadLocal对象本身
map.set(this, value);
else
//根据当前线程初始化它的ThreadLocalMap并设置值
createMap(t, value);
}
2. get() 方法
/**
* Returns the value in the current thread's copy of this thread-local variable.
* If the variable has no value for the current thread,it is first initialized to
* the value returned by an invocation of the {@link #initialValue} method.
* 返回当前线程在当前ThreadLocak中所对应的线程局部变量
* 若当前值不存在,则返回initialValue方法设置的初始默认值
* @return the current thread's value of this thread-local
*/
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程持有的Map
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
//Map为空或值为空,返回默认值
return setInitialValue();
}
3. remove() 方法
/**
* Removes the current thread's value for this thread-local variable.
* If this thread-local variable is subsequently {@linkplain #get read}
* by the current thread, its value will be reinitialized by invoking its
* {@link #initialValue} method, unless its value is {@linkplain #set set}
* by the current thread in the interim. This may result in multiple invocations
* of the {@code initialValue} method in the current thread.
* 移除当前线程在当前ThreadLocal中对应的私有变量
* 当该变量之后被当前线程读取(get),该值会重新被initialValue方法初始化除非这期间被set
* 这将会导致initialValue方法会被当前线程多次调用
* @since 1.5 该方法是JDK1.5新增方法
*/
public void remove() {
//获取当前线程持有的ThreadLocalMap,由此可见get和set中的相关代码也应该合并为一行
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//注意是从ThreadLocalMap移除的当前ThreadLocal对象(即ThreadLocalMap的key)
m.remove(this);
}
4. getMap, createMap 方法
/**
* 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;
} /**
* 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
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
5. nextHashCode 方法
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
6. initialValue 方法
/**
* Returns the current thread's "initial value" for this thread-local variable.
* This method will be invoked the first time a thread accesses the variable
* with the {@link #get} method, unless the thread previously invoked the {@link #set}
* method, in which case the <tt>initialValue</tt> method will not be invoked for the thread.
* Normally, this method is invoked at most once per thread, but it may be invoked again
* in case of subsequent invocations of {@link #remove} followed by {@link #get}.
* 返回当前线程在当前ThreadLocal中的初始默认值
* 第一次get操作会调用该方法,除非之前已经调用了set方法(即已有值)
* 一般情况下该方法只会被执行一次,但有可能出现多次,比如:
* 调用remove方法之后调用了get方法
* <p>This implementation simply returns <tt>null</tt>; if the programmer desires
* thread-local variables to have an initial value other than <tt>null</tt>,
* <tt>ThreadLocal</tt> must be subclassed, and this method overridden.
* Typically, an anonymous inner class will be used.
* 该方法默认返回null,可以重写该方法(比如继承或实现一个匿名类)
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
---------------
/** 比如 自定义一个String类型的匿名ThreadLocal**/
ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "I am roman";
}
};
■ ThreadLocalMap - 线程隔离的秘密
- ThreadLocalMap是一个专门为线程本地变量设计的一个特殊的哈希表
- ThreadLocalMap的key为ThreadLocal,value即为要保存的变量的值
- 每个线程都有一个私有的ThreadLocalMap对象,其可以存放多个不同ThreadLocal作为key的键值对
- ThreadLocalMap采用的是开地址法而不是链表来解决冲突,并要求容量必须是2次幂
1. 类定义
static class ThreadLocalMap
2. Entry
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
* 它使用主要的引用域作为自身的key(即ThreadLocal对象)
* 由于Entry继承自WeakReference,而ThreadLocal被WeakReference封装
* !!重点:因此Entry的Key才是弱引用(而不是Entry)!!(笔者在内存泄露会进一步阐述)
* 当调用get方法返回null时,这意味着该key不再被引用,因此该entry将会从数组中移除
* 弱引用:当JVM在GC时如果发现弱引用就会立即回收
* 比较有意思的是Entry并没有使用 HashMap.Entry 的链表结构
* 感兴趣的读者可先思考ThreadLocalMap是如何处理 hash冲突的问题(后面就讲解)
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
//当ThreadLocal的外部强引用被回收时,ThreadLocalMap的key会变成null
//注意key是个ThreaLocal对象,但因为key被WeakReference封装,因此才具有弱引用特性
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
3. 重要的内部元素
/**
* The initial capacity -- MUST be a power of two.
* 容量必须2次幂,服务于Hash算法
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary. table.length MUST always be a power of two.
* 底层实现还是一个Entry数组
*/
private Entry[] table;
/**
* The number of entries in the table.
* 数组已有元素数量
*/
private int size = 0;
/**
* The next size value at which to resize.
* 阈值,默认为0
*/
private int threshold; // Default to 0
4. 构造器
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
* 默认构造器,包含一个键值对:一个ThreadLocal类型的key,一个任意类型的value
* createMap方法会直接使用该构造器一次性完成ThreadLocalMap的实例化和键值对的存储
*/
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//计算数组下标 跟HashMap的 index = key.hashCode() & (cap -1) 保持一致(即取模运算优化版)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//在数组指定下标处填充数组
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);//默认阈值是 32/3 约等于 10.6667
}
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
* 取len的三分之二,而不是HashMap的0.75
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
5. ThreadLocalMap 一些重要方法
1) set 方法
/**
* Set the value associated with key.
* 存储键值对,比较有趣的是Entry并不是链表,这意味着ThreadLocalMap底层只是数组
* 其解决冲突(或者说散列优化)的关键在于神奇的0x61c88647
* 若遇到过期槽,就占用该过期槽(会涉及位移和槽清除操作)
* 当清理成功同时到达阈值,需要扩容
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;//数组容量
//计算数组下标 跟HashMap的 index = key.hashCode() & (cap -1) 保持一致(即取模运算优化版)
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
//若key已存在,替换值即可
if (k == key) {
e.value = value;
return;
}
//若当前槽为过期槽,就清除和占用该过期槽
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
//否则继续往后 直到找到key相等或第一个过期槽为止
}
tab[i] = new Entry(key, value);
int sz = ++size;
//当清理成功同时到达阈值,需要扩容
//cleanSomeSlots要处理的量是已有元素数量
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Increment i modulo len. 不超过长度就自增1
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
2) remove 方法
/**
* Remove the entry for key.
* 当找到该元素的时候,主要做了两个清洗操作
* 1.将key(ThreadLocal)设置为null
* 2.当前槽变成过期槽,因此要清除当前槽所存储的Entry元素(主要是避免内存泄露)
*/
private void remove(ThreadLocal key) {
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)]) {
if (e.get() == key) {
e.clear();//会将key设为null -> this.referent = null
expungeStaleEntry(i);//清除过期元素
return;
}
}
}
■ 碰撞解决与神奇的0x61c88647
- 机智的读者肯定发现ThreadLocalMap并没有使用链表或红黑树去解决hash冲突的问题,而仅仅只是使用了数组来维护整个哈希表,那么重中之重的散列性要如何保证就是一个很大的考验
- ThreadLocalMap 通过结合三个巧妙的设计去解决这个问题:
1. Entry的key设计成弱引用,因此key随时可能被GC(也就是失效快),尽量多的面对空槽
2. (单个ThreadLocal时)当遇到碰撞时,通过线性探测的开放地址法解决冲突问题
3. (多个ThreadLocal时)引入了神奇的0x61c88647,增强其的散列性,大大减少碰撞几率 - 之所以不用累加而用该值,笔者认为可能跟其找最近的空槽有关(跳跃查找比自增1查找用来找空槽可能更有效一些,因为有了更多可选择的空间spreading out),同时也跟其良好的散列性有关
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
* 为了让哈希码能均匀的分布在2的N次方的数组里
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
* 每个ThreadLocal的hashCode每次累加HASH_INCREMENT
*/
private static int nextHashCode() {
//the previous id + our magic number
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
■ ThreadLocal 的实现机制
- 每个线程都拥有一个ThreadLocalMap对象,即 ThreadLocal.ThreadLocalMap threadLocals = null
- 每一个ThreadLocal对象有一个创建时生成唯一的HashCode,即 nextHashCode(),通过取模确定所在槽下标位置
- 访问一个ThreadLocal变量的值,即是查找ThreadLocalMap中对应键值对,即key为该ThreadLocal的键值对
- 由于一个ThreadLocalMap可以拥有很多个ThreadLocal,推导可得一个线程可拥有多个ThreadLocal(或者说拥有多个不同ThreadLocal作为key的键值对)
//可以定义多个ThreadLocal,每个线程都拥有自己私有的各种泛型的ThreadLocal
//比如线程A可同时拥有以下三个ThreadLocal对象作为key
ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
ThreadLocal<Object> objectThreadLocal = new ThreadLocal<Object>();
ThreadLocal<Integer> intThreadLocal = new ThreadLocal<Integer>();
PS:
***** 后续会和大家讨论一些内存泄露与ThreadLocal 的实际应用问题,请多指教!! ************
*** 预定:项目中使用 Task,本地线程变量,保证任务的正常运行(待续)
ThreadLocal 线程本地变量 及 源码分析的更多相关文章
- Java并发机制(4)--ThreadLocal线程本地变量(转)
个人理解: 说明:看了博客园中大神写的ThreadLocal的详解,感觉还是有些迷糊,下面用自己的理解简单描述下ThreadLocal的机制(难免有误): 1.首先ThreadLocal用于存储对应线 ...
- Threadlocal线程本地变量理解
转载:https://www.cnblogs.com/chengxiao/p/6152824.html 总结: 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪 用法:在切面中生 ...
- Android线程间异步通信机制源码分析
本文首先从整体架构分析了Android整个线程间消息传递机制,然后从源码角度介绍了各个组件的作用和完成的任务.文中并未对基础概念进行介绍,关于threadLacal和垃圾回收等等机制请自行研究. 基础 ...
- Java线程池ThreadPoolExector的源码分析
前言:线程是我们在学习java过程中非常重要的也是绕不开的一个知识点,它的重要程度可以说是java的核心之一,线程具有不可轻视的作用,对于我们提高程序的运行效率.压榨CPU处理能力.多条线路同时运行等 ...
- Java ThreadPoolExecutor线程池原理及源码分析
一.源码分析(基于JDK1.6) ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉.在Jdk1.6中,Thread ...
- java ThreadLocal线程设置私有变量底层源码分析
前面也听说了ThreadLocal来实现高并发,以前都是用锁来实现,看了挺多资料的,发现其实还是区别挺大的(感觉严格来说ThreadLocal并不算高并发的解决方案),现在总结一下吧. 高并发中会出现 ...
- lesson1:threadlocal的使用demo及源码分析
本文中所使用的demo源码地址:https://github.com/mantuliu/javaAdvance 其中的类Lesson1ThreadLocal 本文为java晋级系列的第一讲,后续会陆续 ...
- ThreadLocal 工作原理、部分源码分析
1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...
- Java线程池ThreadPoolExecutor类源码分析
前面我们在java线程池ThreadPoolExecutor类使用详解中对ThreadPoolExector线程池类的使用进行了详细阐述,这篇文章我们对其具体的源码进行一下分析和总结: 首先我们看下T ...
随机推荐
- O(N^2)最长上升子序列
//最长上升子序列o(N^2)可以不连续的子序列, //状态为maxlen[i]表示以a[i]为终点最大上升子序列长度 #include<iostream> #include<cst ...
- python 爬取国家粮食局东北地区玉米收购价格监测信息
#!/usr/bin/python# -*- coding: UTF-8 -*-import reimport sysimport timeimport urllibimport urllib.req ...
- 运行期以索引获取tuple元素-C++14(原创)
在编译期很容易根据索引来获取对应位置的元素,因为 tuple 的帮助函数 std::get<N>(tp) 就能获取 tuple 中第 N 个元素.然而我们却不能直接在运行期通过变量来获取 ...
- 用maven搭建java ee项目
一.开发环境 jdk1.7 tomcat7 eclipse-jee-luna-R-win32 maven2.2.1 二搭建步骤 1.点击File->New->Other,选择maven ...
- Android之本地相冊图片选取和拍照以及图片剪辑
转载请注明出处:http://blog.csdn.net/loveyaozu/article/details/51160482 相信有非常多Android开发者在日常开发中,因为项目需求,须要我们的A ...
- java 可变參数
我们在某些特定的需求环境下,可能要对某一个方法中的參数进行一些操作,并且这些方法中的參数是不规定的,那么问题来了,我们该怎么办呢? java事实上就为我们考虑了这样的情况,那就是使用可变參数 可变參数 ...
- 介绍一个法国的时间戳server
特别说明: 以下介绍的法国时间戳server已经停止服务了.我曾发Email给相关站点的管理员.对方回复说他也不知道什么时候能恢复服务,有可能就是遥遥无期了.所以以下的内容仅有參考价值.没法实践了. ...
- LINUX6安装Oracle10g无法启动安装界面解决
***********************************************声明*************************************************** ...
- hdu5418--Victor and World(floyd+状压dp)
题目链接:点击打开链接 题目大意:有n个城市.在n个城市之间有m条双向路.每条路有一个距离.如今问从1号城市去游览其他的2到n号城市最后回到1号城市的最短路径(保证1能够直接或间接到达2到n).(n& ...
- JAVA入门[6]-Mybatis简单示例
初次使用Mybatis,先手写一个hello world级别的例子,即根据id查询商品分类详情. 一.建表 create table Category ( Id INT not null, Name ...