■ 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 线程本地变量 及 源码分析的更多相关文章

  1. Java并发机制(4)--ThreadLocal线程本地变量(转)

    个人理解: 说明:看了博客园中大神写的ThreadLocal的详解,感觉还是有些迷糊,下面用自己的理解简单描述下ThreadLocal的机制(难免有误): 1.首先ThreadLocal用于存储对应线 ...

  2. Threadlocal线程本地变量理解

    转载:https://www.cnblogs.com/chengxiao/p/6152824.html 总结: 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪 用法:在切面中生 ...

  3. Android线程间异步通信机制源码分析

    本文首先从整体架构分析了Android整个线程间消息传递机制,然后从源码角度介绍了各个组件的作用和完成的任务.文中并未对基础概念进行介绍,关于threadLacal和垃圾回收等等机制请自行研究. 基础 ...

  4. Java线程池ThreadPoolExector的源码分析

    前言:线程是我们在学习java过程中非常重要的也是绕不开的一个知识点,它的重要程度可以说是java的核心之一,线程具有不可轻视的作用,对于我们提高程序的运行效率.压榨CPU处理能力.多条线路同时运行等 ...

  5. Java ThreadPoolExecutor线程池原理及源码分析

    一.源码分析(基于JDK1.6) ThreadExecutorPool是使用最多的线程池组件,了解它的原始资料最好是从从设计者(Doug Lea)的口中知道它的来龙去脉.在Jdk1.6中,Thread ...

  6. java ThreadLocal线程设置私有变量底层源码分析

    前面也听说了ThreadLocal来实现高并发,以前都是用锁来实现,看了挺多资料的,发现其实还是区别挺大的(感觉严格来说ThreadLocal并不算高并发的解决方案),现在总结一下吧. 高并发中会出现 ...

  7. lesson1:threadlocal的使用demo及源码分析

    本文中所使用的demo源码地址:https://github.com/mantuliu/javaAdvance 其中的类Lesson1ThreadLocal 本文为java晋级系列的第一讲,后续会陆续 ...

  8. ThreadLocal 工作原理、部分源码分析

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  9. Java线程池ThreadPoolExecutor类源码分析

    前面我们在java线程池ThreadPoolExecutor类使用详解中对ThreadPoolExector线程池类的使用进行了详细阐述,这篇文章我们对其具体的源码进行一下分析和总结: 首先我们看下T ...

随机推荐

  1. Mysql实现企业级数据库主从复制架构实战

    场景 公司规模已经形成,用户数据已成为公司的核心命脉,一次老王一不小心把数据库文件删除,通过mysqldump备份策略恢复用了两个小时,在这两小时中,公司业务中断,损失100万,老王做出深刻反省,公司 ...

  2. Nginx负载均衡的优缺点

    Nginx的优点是: 1.工作在网络的7层之上,可以针对http应用做一些分流的策略,比如针对域名.目录结构,它的正则规则比HAProxy更为强大和灵活,这也是它目前广泛流行的主要原因之一,Nginx ...

  3. indexOf 和 lastIndexOf的区别

    indexOf 和  lastIndexOf 是什么? indexOf 和 lastIndexOf 都是索引文件 indexOf 是查某个指定的字符串在字符串首次出现的位置(索引值) (也就是从前往后 ...

  4. 大白话Vue源码系列(01):万事开头难

    阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的 ...

  5. 浏览器Agent大全 (含IE 11, Edge)

    Edge mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/51.0.27 ...

  6. C语言写单链表的创建、释放、追加(即总是在最后的位置增加节点)

    昨天周末给学妹讲了一些指针的知识,本来我对指针就是似懂非懂的状态,经过昨天一讲,我对指针的学习就更深刻了 果然给别人讲课也是学习的一个方法.加上最近复习数据结构,发现我的博客里没有链表的博文,所以趁这 ...

  7. TP3.2 图片上传及缩略图

    基于TP自带的上传文件的类, Think/Upload.class.php 设置表单的enctype属性 下面是上传的具体方法 /** * 图片上传处理 * @param [String] $path ...

  8. es6+require混合开发,兼容es6 module,import,export

    近一年,一直很忙,做了不少的项目,不过都不是太满意,毕竟是别人的作品,不好意思写出来.最近打算开发一个es6的项目,项目中用到require,本文主要讲解es6的module规范怎么与require的 ...

  9. iKcamp团队制作|基于Koa2搭建Node.js实战(含视频)☞ 中间件用法

    中间件用法--讲解 Koa2 中间件的用法及如何开发中间件

  10. [开源项目] Android校验库 - FireEye

    简单易用的Android校验库. 这是一个简单Android校验库,按配置来验证用户输入的表单信息. 仅仅须要几行代码,就可以验证用户输入,而且将验证错误反馈给用户. 它内置了大量经常使用的验证类型, ...