网上有很多关于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. JavaScript学习系列博客_16_JavaScript中的函数(Function)简介

    函数(Function) - 函数也是一个对象,也具有普通对象的功能 - 函数中可以封装一些代码,在需要的时候可以去调用函数来执行这些代码:当调用函数时,函数中封装的代码会按照顺序执行. - 使用ty ...

  2. springMVC入门(二)------springMVC入门案例

    简介 本案例主要完成了springMVC的基本配置,可针对响应的HTTP URL返回数据与视图 一.###web.xml的配置 要使springMVC生效,首先需要对web.xml进行配置,配置spr ...

  3. Rethinking the performance comparison between SNNS and ANNS

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! Abstract ANN是通向AI的一种流行方法,它已经通过成熟的模型,各种基准,开源数据集和强大的计算平台获得了非凡的成功.SNN是一类 ...

  4. SPSSAU数据分析思维培养系列3:分析思路

    本文章为SPSSAU数据分析思维培养的第3期文章. 上文讲解如何选择正确的分析方法,除了有正确的分析方法外,还需要把分析方法进行灵活运用.拿到一份数据,应该如何进行分析,总共有几个步骤,第一步第二步应 ...

  5. 漏洞重温之sql注入(五)

    漏洞重温之sql注入(五) sqli-labs通关之旅 填坑来了! Less-17 首先,17关,我们先查看一下页面,发现网页正中间是一个登录框. 显然,该关卡的注入应该为post型. 直接查看源码. ...

  6. 模型层中QuerySet的学习

    创建对象 使用关键字参数实例化模型实例来创建一个对象,然后调用save()把它保存到数据库中 pub_obj = models.Publisher(title='奥利给出版社') pub_obj.sa ...

  7. 广度优先搜索(BFS)解题总结

    定义 广度优先搜索算法(Breadth-First-Search),是一种图形搜索算法. 简单的说,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点. 如果所有节点均被访问,则算法中止. B ...

  8. Centos7安装Oracle12c教程

    12c数据库 创建oracle的系统用户和用户组 [root@localhost /]# groupadd oinstall [root@localhost /]# groupadd dba [roo ...

  9. React.Fragment

    React 中一个常见模式是为一个组件返回多个元素.Fragments 可以让你聚合一个子元素列表,并且不在DOM中增加额外节点. Fragments 看起来像空的 JSX 标签: render() ...

  10. Android开发之TextView中间设置横线,适用于电商项目,商品原价之类的功能。

    textview.getPaint().setFlags(Paint. STRIKE_THRU_TEXT_FLAG ); //中间横线 textview.getPaint().setFlags(Pai ...