网上有很多关于ThreadLocal的介绍,有的介绍比较简单,也有的介绍很复杂,比较难懂,今天,自己结合它的源码,也做个简易梳理,记录如下!

ThreadLocal的作用

在多请求并发访问过程中,我们往往需要将一个指定变量隔离起来,达到只对当前线程可用,其他线程不可用的效果,因此,我们就会使用到ThreadLocal来实现。

实现原理其实就是在每个线程中维护了一个Map结构(ThreadLocalMap,它是ThreadLocal中的静态内部类),ThreadLocal对象为Key,需要隔离的值为Value。为了达到线程全局可用,我们往往将ThreadLocal声明为全局静态变量。

Thread中的ThreadLocalMap对象

那么ThreadLocal具体如何做到线程隔离的?我们下面做具体分析!

ThreadLocal

我们暂时先不分析ThreadLocalMap,单独来看ThreadLocal的几个方法源码介绍!

1.对象初始化

ThreadLocal初始化比较简单!

public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

我们往往在初始化时会给他指定一个默认值,不指定的话,默认值为null,这里有两种指定方式:

第一种:直接复写ThreadLocal中的initialValue方法

第二种:利用函数式编程,创建SuppliedThreadLocal对象,由get方法直接返回初始值

public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "Test";
}
};
public static final ThreadLocal<String> THREAD_LOCAL = ThreadLocal.withInitial(()->"Test");

SuppliedThreadLocal对象是对ThreadLocal的一个特定实现,通过构造函数传入Supplier,再由实现的initialValue方法返回supplier.get()的结果,其他也没什么可多介绍的。

2.获取变量

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

从get方法我们可以看到,ThreadLocal是从当前线程中获取到了ThreadLocalMap对象,然后取出其中的Entry.Value值,如果对象不存在就返回初始值,初始化方法initialValue会在这里调用一次,其他操作不再调用。

3.设置变量

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

set方法与get方法一样,会通过当前线程取出ThreadLocalMap对象,然后将当前ThreadLocal对象作为Key,存储Value值,ThreadLocalMap不存在时,会创建新的Map。

4.移除变量

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

移除变量同样是根据currentThread来找到的Map,然后对当前ThreadLocal做remove操作。

ThreadLocalMap

通过ThreadLocal的操作介绍我们可以看到,ThreadLocal的操作都是基于ThreadLocalMap来实现的,所以,ThreaLocalMap才是我们对ThreadLocal变量实现线程隔离的重点。

1.Entry

ThreadLocalMap中存储数据关系的是Entry,它的Key是ThreadLocal对象,采用弱引用,Value是一个强引用对象Object。当Entry.get()获取的ThreadLocal为Null时,GC回收将直接清除该对象,但Value对象,需要我们手动清除,所以,我们需要在每个ThreadLocal调用结束时,执行remove方法。关于弱引用与强引用的关系以及他们的对象回收机制,这里不做过多介绍,有兴趣的同学可以自行学习!

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

2.初始化

ThreadLocalMap的操作是基于Entry[]数组table完成的,数组初始化大小为16。table是一个2的N次方的数组,ThreadLocal通过AtomicInteger类型的nextHashCode,每次偏移HASH_INCREMENT=0x61c88647的大小来实现数据在数组上的平均分布。

关于Entry[]中如何解决碰撞冲突问题,可以参考:ThreadLocal 和神奇的数字 0x61c88647

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}

3.获取Entry

private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}

查询table中的Entry值时,采用神奇的0x61c88647,ThreadLocal对象作为Key与Entry的Key相同时,返回此Entry,否则,会从i开始迭代查找Entry。

4.设置Entry

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);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

set方法中兼容新增与修改操作,如果找到同一个ThreadLocal对应的Entry时,则直接重新赋值Value,否则新建Entry执行table[i]。

5.移除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();
expungeStaleEntry(i);
return;
}
}
}

remove操作同样通过循环table,找到Key相同的ThreadLocal对象,然后通过指定弱引用的Key值为Null移除,并将table[i].value也置为Null。

持续更新中...

结合源码谈谈ThreadLocal!的更多相关文章

  1. 深入源码理解ThreadLocal和ThreadLocalMap

    一.ThreadLoacl的理解: 官方的讲: ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰 通俗的讲: Thre ...

  2. JDK源码之ThreadLocal

    1.定义 ThreadLocal是线程变量,就是说每一个线程都有对应的该变量副本,线程修改该变量时,线程与线程之间的变量是相互隔离的,互相并看不见.这个结构附带在线程上,一个线程可以根据ThreadL ...

  3. 从源码理解 ThreadLocal

    为每个线程保存各自的拷贝,可以通过在Thread类中定义一个成员变量来保存每个线程值,这样也是线程安全的. 通过定义一个成员变量 sn 来实现,这里并没有使用ThreadLocal类来实现: publ ...

  4. Netty源码分析-- ThreadLocal分析(九)

    为了更好地探讨Netty的内存模型,后面会用到,这里我还是决定跟大家一起看下ThreadLocal和FastThreadLocal的源码,有的时候我们在看源码的时候会一层层的遇到很多之前没有看过的内容 ...

  5. Java读源码之ThreadLocal

    前言 JDK版本: 1.8 之前在看Thread源码时候看到这么一个属性 ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal实现的是 ...

  6. 从 源码 谈谈 redux compose

    compose,英文意思 组成,构成. 它的作用也是通过一系列的骚操作,实现任意的.多种的.不同的功能模块的组合,用来加强组件. 看看源码 https://github.com/reactjs/red ...

  7. ThreadLocal 源码剖析

    ThreadLocal是Java语言提供的用于支持线程局部变量的类.所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量(每个线程一个拷贝).在各个Java web的各种框架 ...

  8. Java多线程9:ThreadLocal源码剖析

    ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...

  9. 多线程爬坑之路-ThreadLocal源码及原理的深入分析

    ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. This class provides thread-l ...

随机推荐

  1. Flutter FlatButton 按钮基本各种用法

    Flutter中给我们预先定义好了一些按钮控件给我们用,常用的按钮如下 RaisedButton :凸起的按钮,其实就是Android中的Material Design风格的Button ,继承自Ma ...

  2. linux命令--大小写转换命令

    1.tr命令 tr命令转换小写为大写     cat aa.txt | tr a-z A-Z 或者   cat aa.txt | tr [:lower:] [:upper:]  tr命令大写转换小写  ...

  3. springMVC入门(一)------springMVC基本概念与安装

    springMVC简介 springMVC是一个基于MVC的web框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面. springMVC安装 本例中使用 ...

  4. python元组的概念与基本操作

    元组与列表类似,关于元组同样需要做如下三点: A.概念 1.元组通过英文状态下的圆括号构成“()”.其存放元素与列表一样,可以是不通的数值类型,也可以是不通的数据结构. 2.元组仍然是一种序列,所以几 ...

  5. Java中抽象类和接口的介绍及二者间的区别

    接口(Interface)和抽象类(Abstract Class)是支持抽象类定义的两种机制. 一.抽象类 在Java中被abstract关键字修饰的类称为抽象类,被abstract关键字修饰的方法称 ...

  6. Charles 断点修改Response

    前言: 我们可以通过map功能进行重定向,但如果同一个域名进行的是不同请求与返回.此时map在这里就不适用了. 我们可以通关对某一请求进行断点,在进行修改请求或者返回.这样就可以满足我们的需求了. 一 ...

  7. 巧用HashMap一行代码统计单词出现次数

    简介 JDK是在一直在迭代更新的,很多我们熟悉的类也悄悄的添加了一些新的方法特性.比如我们最常用的HashMap. 今天给大家讲一下HashMap在JDK8中添加的两个新方法compute和merge ...

  8. google protocol buffer——protobuf的编码原理二

    这一系列文章主要是对protocol buffer这种编码格式的使用方式.特点.使用技巧进行说明,并在原生protobuf的基础上进行扩展和优化,使得它能更好地为我们服务. 在上一篇文章中,我们主要通 ...

  9. python os库的使用方法 + 自动化安装第三方库脚本

    一.os库基本介绍 os库提供通用的.基本的操作系统交互功能,包括windows.Mac os.linux os库是python标准库,包含几百个函数 常用路径操作.进程管理.环境参数等几类 路径操作 ...

  10. 【Go语言入门系列】(八)Go语言是不是面向对象语言?

    [Go语言入门系列]前面的文章: [Go语言入门系列](五)指针和结构体的使用 [Go语言入门系列](六)再探函数 [Go语言入门系列](七)如何使用Go的方法? 1. Go是面向对象的语言吗? 在[ ...