ThreadLocal知识点解析
1. 作用
- (具体作用)为每个线程都维护一个* 线程局部变量* ;
- (目的)每个线程都可以修改自己内部的变量本,而不影响其他线程的变量。
- (重要区分:optional)属于线程安全范畴,但是不是为了解决线程同步问题:多个线程对同一个资源的并发访问。
2. 每个线程的局部变量是存储在哪里的?
类似问题:
ThreadLocal是如何每个线程维护局部变量的?
==每个线程都保留了一个对ThreadLocalMap的引用(threadlocals),由该ThreadLocalMap存储该线程的变量副本,而该ThreadLocalMap每个entry中的key为ThrealLocal(通过threadLocalHashCode
保证其唯一性),value为变量副本。==
详细解析如下:
Thread类中有个缺省类型的成员变量ThreadLocalMap threadLocals = null;
在每个线程中,都维护了一个threadlocals对象,在没有ThreadLocal变量的时候是null的。一旦在ThreadLocal的createMap函数中初始化之后,这个threadlocals就初始化了。
//Threadlocal.class
void createMap(Thread var1, T var2) {
var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
}
- 什么时候调用createMap()?
调用ThreadLocal的get函数或set函数时,而threadlocals为null时,那么ThreadLocal会去调用createMap()
以后每次那个ThreadLocal对象想要访问变量的时候,比如set函数和get函数,都是先通过getMap(当前线程)函数,先将线程的map取出,然后再从这个map中取出数据【以当前threadlocal作为参数】。
//Threadlocal.class
//get()调用过程
public T get() {
Thread var1 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
if(var2 != null) { // ThreadLocalMap已经被创建
ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
if(var3 != null) {
Object var4 = var3.value;
return var4;
}
}
return this.setInitialValue(); // ThreadLocalMap为空或者ThreadLocal不在ThreadLocalMap的键中
}
private T setInitialValue() {
Object var1 = this.initialValue(); // var1 = null;
Thread var2 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if(var3 != null) {
// ThreadLocal不在ThreadLocalMap中, 新建Entry,key为该ThreadLocal,value为空
var3.set(this, var1);
} else {
// ThreadLocalMap为空
this.createMap(var2, var1);
}
return var1;
}
//set()调用过程
public void set(T var1) {
Thread var2 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if(var3 != null) {
var3.set(this, var1);
} else {
this.createMap(var2, var1);
}
}
有趣的是,Thread本身除了在线程退出时(exit())重新将threadlocal置null,其他时候对threadlocal的访问均由ThreadLocal的getMap()提供。
// Threadlocal.class
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
return var1.threadLocals;
}
3. 每个线程的局部变量是怎么存储的?
局部变量存储在ThreadLocalMap中,该Map其实是一个Entry类的数组,每个Entry均以该局部变量的ThreadLocal作为key,具体的数据为value的key-value对。当ThreadLocal发生冲突时,将采用“线性探测法”来解决冲突。
ThreadLocalMap详解
ThreadLocalMap是定义在ThreadLocal静态内部类,其中 ThreadLocal.ThreadLocalMap.Entry[] table
就是用于存储线程局部变量。
Entry类是ThreadLocalMap的静态内部类,用于存储数据。它的源码如下:
// ThreadLocal.ThreadLocalMap.class
/**
* 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.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry类继承了WeakReference
// ThreadLocal.ThreadLocalMap.class
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
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;
// 这相当于取模运算hashCode % size的一个更高效的实现.
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);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
i = (n - 1) & hash来计算当前Entry存放的位置(index),而老版本的JDK使用i = hash % n,只有当n为2的整数次幂的时候,这两个计算((n - 1) & hash和hash%n)才能等价。使用按位与&取代取模%主要是因为&一般是单周期指令而%需要用到除法器,速度相差好几倍。
4. 为什么ThreadLocalMap的键是ThreadLocal而不是Thread呢?
假如我们把ThreadLocalMap做成一个Map<t extends Thread, ?>
类型的Map,那么它存储的东西将会非常多(相当于一张全局线程本地变量表),这样的情况下用线性探测法解决哈希冲突的问题效率会非常差。
5. 典型的应用场景
Spring对一些Bean中的成员变量采用ThreadLocal进行处理,让它们可以成为线程安全的。举个例子:
package org.springframework.web.context.request;
public abstract class RequestContextHolder {
private static final boolean jsfPresent =
ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<RequestAttributes>("Request attributes");
private static final ThreadLocal<RequestAttributes> 16M5mvqRwbyuTpLduM9yWFa5PWocJXnEUN =
new NamedInheritableThreadLocal<RequestAttributes>("Request context");
//......下面省略
}
几个十分值得入门的链接:
Java ThreadLocal的使用
并发编程 | ThreadLocal源码深入分析
上一篇: java面试题(3)
下一篇: BAT常问面试题JVM
推荐阅读
-
iOS中json解析出现的null,nil,NSNumber的解决办法
-
netty源码解析(4.0)-28 ByteBuf内存池:PooledByteBufAllocator-把一切组装起来
-
JavaScrpt的面向对象全面解析
-
SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
-
????「课代表」帮你总结了全网最全的Redis知识点
-
SpringBoot 源码解析 (六)----- Spring Boot的核心能力 - 内置Servlet容器源码分析(Tomcat)
-
MyBatis启动之XMLConfigBuilder解析配置文件(二)
-
C#解析Lrc歌词文件过程详解
-
android 解析json数据格式的方法
-
Android Intent的几种用法详细解析