源码中对于ThreadLocal类的解释是:
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/
//几个静态常量
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

从上面的官方解释中我们可以得出三点

  • 这个类提供线程局部变量
  • 这些变量与普通的变量不同,因为每个访问一个变量的线程(通过其{@code get}或{@code set}方法)都有自己的、独立初始化的变量副本。
  • {@code ThreadLocal}实例通常是希望将状态与线程关联的类中的私有静态字段。
从表面看,ThreadLocal可以理解为一个Map对象,当前线程作为key,需要存储的对象作为value,可仔细研究过源码后,ThreadLocal本质是通过它的静态内部类ThreadLocalMap来为每个线程维护一个数组table,即Entryp[],当前线程的threadLocalHashCode值作为数组下标。
稍后介绍ThreadLocal的添加(set(T value))、获取(get())、删除(remove())等方法的实现,看完这篇介绍,就会理解ThreadLocal的实现方式,不怕被面试官吊打了。
 
添加实现set(T value)
public void set(T value) {
//获取当前线程Thread
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

通过阅读源码,可以知道ThreadLocal的set方法主要分为以下几个步骤

  • 获取当前线程t
  • 通过当前线程使用getMap(t)方法获取当前ThreadLocal的静态内部类ThreadLocalMap 对象map,如果map存在,则调用map.set(this, value)直接保存,否则调用createMap(t, value)方法先创建ThreadLocalMap 对象,在创建的过程中进行保存。
 
删除remove()实现
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

ThreadLocal的remove方法比较简单,就是通过当前Thread对象获取到ThreadLocalMap对象,如果不为null的话,调用ThreadLocalMap的remove方法实现。

 
获取get()实现
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();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}

通过阅读源码,可以知道ThreadLocal的get方法主要分为以下几个步骤

  • 获取当前线程t
  • 通过当前线程使用getMap(t)方法获取当前ThreadLocal的静态内部类的静态内部类ThreadLocalMap 对象map,如果map不等于null,map.getEntry(this)通过获取ThreadLocalMap.Entry对象,返回Entry对象的value值;如果map为null,则调用setInitialValue()方法去设置ThreadLocalMap初始值,最后结果返回null。
 
 
 
简要介绍下ThreadLocalMap
ThreadLocalMap仅用于维护线程本地值的Map,仅作为ThreadLocal的私有静态内部类。
//默认的table容量
private static final int INITIAL_CAPACITY = 16;
//Entry数组,创建ThreadLocalMap的时候会创建一个默认容量的table数组
private Entry[] table;
//当前Entry的长度
private int size = 0;
//下一个要调整大小的值
private int threshold; // Default to 0

上面的四个常量,记录着ThreadLocalMap的状态变化。

 
ThreadLocalMap.set(ThreadLocal<?> key, Object value)
//添加具体实现,该方法为ThreadLocalMap的方法
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数组
Entry[] tab = table;
int len = tab.length;
//获取索引
int i = key.threadLocalHashCode & (len-1);
//遍历tab,如果存在直接更新
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;
//满足条件数组扩容x2
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

该方法是ThreadLocal的set(T value)的具体实现,通过阅读源码可以知道set(ThreadLocal<?> key, Object value)的实现具体分为以下几个步骤

  • 获取当前table数组tab、tab当前长度、根据当前线程k的threadLocalHashCode值获取索引i
  • 遍历tab如果已经存在Entry对象e,通过e获取当前Threadlocal对象k。
    • 如果k和传入的key相等,则将传入的value赋值给e.value直接更新;
    • 如果k == null,说明threadLocal强引用已经被释放掉,那么就无法不能通过key去访问到相应的value,说明此时存在脏Entry,可以理解为内存泄漏,则调用replaceStaleEntry(key, value, i)去处理,用当前插入的值替换掉这个key为null的“脏”entry。
  • 如果当前tab[i]不存在,则使用new Entry(key, value)创建新的Entry放进tab[i],修改size,插入后调用cleanSomeSlots(int i, int n)再次清除一些key为null的“脏”entry,如果大于阈值就需要进行扩容。
 
ThreadLocalMap.getEntry(ThreadLocal<?> key)
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);
} private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length; while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}

该方法是ThreadLocal的get()的具体实现,通过阅读源码后可以知道具体实现步骤

  • 根据传入的key获取索引
  • 根据索引获取Entry  e
    • 如果e不为null,并且e.get()得到的Threadlocal等于传入的key,则返回e
    • 如果e为null,则调用getEntryAfterMiss(key, i, e)方法从tab第i个entry往后遍历,找到对应的key == e.get()则返回e;如果e.get()为null,则调用expungeStaleEntry(i)进行清理;如果e.get()不为null也不等于key,重新计算索引得到e;
    • 如果e为null则直接返回null。
 
ThreadLocalMap.remove(ThreadLocal<?> key)
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;
}
}
}

该方法是ThreadLocal的remove()的具体实现,通过阅读源码后可以知道具体实现步骤

  • 根据传入的key获取索引
  • 遍历tab,如果找到对应的key,则清除当前Entry,再调用expungeStaleEntry(i)进行清理
 
扩展:ThreadLocalMap和Synchronized
  • ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
  • Synchronized同步机制采用了“以时间换空间”的方式,仅提供一份变量,让不同的线程排队访问;而ThreadLocal采用了“以空间换时间”的方式,每一个线程都提供了一份变量,因此可以同时访问而互不影响。
    • 以时间换空间->即枷锁方式,某个区域代码或变量只有一份节省了内存,但是会形成很多线程等待现象,因此浪费了时间而节省了空间。
    • 以空间换时间->为每一个线程提供一份变量,多开销一些内存,但是呢线程不用等待,可以一起执行而相互之间没有影响。
  • ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

谈谈对ThreadLocal类的理解的更多相关文章

  1. ThreadLocal类的理解

    首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.各 ...

  2. Java并发编程之ThreadLocal类

    ThreadLocal类可以理解为ThreadLocalVariable(线程局部变量),提供了get与set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回当 ...

  3. 线程系列5--java中的ThreadLocal类实现线程范围内的数据共享(二)

    ThreadLocal类可以理解成一个类似与map集合使用,以当前线程当做key 来使用,将线程氛围内需要共享的数据当做value,形成键值对的形式使用.ThreadLocal和线程同步机制都是为了解 ...

  4. 理解ThreadLocal类

    1 ThreadLocal是什么 早在JDK 1.2的版本号中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路. 使用这个工具类能够 ...

  5. 深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类

    ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多. 可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内 ...

  6. 理解和使用ThreadLocal类

    一.从数据结构入手 下图为ThreadLocal的内部结构图 从上面的机构图,可以窥见ThreadLocal的核心机制: 每个Thread线程内部都有一个Map: Map里面存储线程本地对象(key) ...

  7. ThreadLocal类详解:原理、源码、用法

    以下是本文目录: 1.从数据库连接探究 ThreadLocal 2.剖析 ThreadLocal 源码 3. ThreadLocal 应用场景 4. 通过面试题理解 ThreadLocal 1.从数据 ...

  8. 用ThreadLocal类实现线程安全的正确姿势

    大家通常知道,ThreadLocal类可以帮助我们实现线程的安全性,这个类能使线程中的某个值与保存值的对象关联起来.ThreadLocal提供了get与set等访问接口或方法,这些方法为每个使用该变量 ...

  9. 2015年11月26日 Java基础系列(三)ThreadLocal类初级学习

    序,ThreadLocal类是为了解决多线程的安全问题.线程安全的意思也就是说每个线程操作自己的变量,不要对其他线程的值造成影响. 在很多情况下,ThreadLocal比直接使用synchronize ...

随机推荐

  1. 从零开始配置webpack(基于webpack 4 和 babel 7版本)

    webpack 核心概念: Entry: 入口 Module:模块,webpack中一切皆是模块 Chunk:代码库,一个chunk由十多个模块组合而成,用于代码合并与分割 Loader:模块转换器, ...

  2. CodeForces - 1047CEnlarge GCD(这题很难,快来看题解,超级详细,骗浏览量)

    C. Enlarge GCD time limit per test1 second memory limit per test256 megabytes inputstandard input ou ...

  3. 题解 bzoj 4398福慧双修(二进制分组)

    二进制分组,算个小技巧 bzoj 4398福慧双修 给一张图,同一条边不同方向权值不同,一条边只能走一次,求从1号点出发再回到1号点的最短路 一开始没注意一条边只能走一次这个限制,打了个从一号点相邻节 ...

  4. 集训模拟赛-1-T2

    好了不要在铺垫了直接整吧就 题目拿来!!!!!!! 倒水 (water) (256MB,1s) [问题描述] 你有一个水桶(记为 0),两个杯子(记为 1,2).水桶中的水量无限,容量也无限.1 号杯 ...

  5. 也谈解决Combobox绑定数据后取值出现System.Data.DataRowView的问题

    刚才遇到一个怪现象:同一个窗口,同一张表,通过第一个Combobox值的改变,动态绑定第二个Combobox,结果出现一个怪现象,第一个Combobox有的值改变第二个Combobox一切正常,有几个 ...

  6. <学习笔记 之 JQuery 基础语法>

    jQuery 库 - 特性 jQuery 是一个 JavaScript 函数库. jQuery 库包含以下特性: HTML 元素选取 HTML 元素操作 CSS 操作 HTML 事件函数 JavaSc ...

  7. 利用github的webhook进行自动部署

    利用github的webhook进行自动部署 github提供了webhook功能,大概意思就是,当你提交了代码,git检测到你进行了push,可以调起你一个你知道的url. 这个功能有什么用了?比如 ...

  8. Spring官网阅读(十二)ApplicationContext详解(中)

    文章目录 1.Spring的资源(Resource) 接口简介 UML类图 抽象基类AbstractResource FileSystemResource AbstractFileResolvingR ...

  9. SpringMVC源码学习:容器初始化+MVC初始化+请求分发处理+参数解析+返回值解析+视图解析

    目录 一.前言 二.初始化 1. 容器初始化 根容器查找的方法 容器创建的方法 加载配置文件信息 2. MVC的初始化 文件上传解析器 区域信息解析器 handler映射信息解析 3. Handler ...

  10. 万盛酒店餐饮管理系统(SpringBoot,SSM,MySQL )

    项目源码获取地址: 链接:https://pan.baidu.com/s/1ip0keQruE2crA8vm1n8ZXQ 提取码:kivb 复制这段内容后打开百度网盘手机App,操作更方便哦 [功能包 ...