源码中对于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. Ceph 12.2.0 实践osd 智能分组功能

    以前我们需要对ssd和hdd进行分组的时候,需要大量的修改crush map,然后绑定不同的存储池到不同的 crush 树上面,现在这个逻辑简化了很多.以上是官方宣传听起来很不错等到12.2.0稳定版 ...

  2. 数学--数论--Find Integer(勾股数定理)

    Problem Description people in USSS love math very much, and there is a famous math problem give you ...

  3. Jenkins 源代码管理(SVN)

    Subversion 安装插件 1.首先将本地的自动化用例打包上传 svn 2.配置 jenkins 源代码管理(每次执行 jenkins 时,会自动 check-ou t配置地址中的代码到 Jenk ...

  4. jmeter4.0,启动jmeter.bat闪退问题

    问题描述: 电脑重装win10系统,配置好了java环境后,解压jmeter的zip包,然后按照网上的教程配置环境变量,然后兴冲冲启动jmeter.bat,结果,闪退,甚至连个报错信息都没有... 然 ...

  5. MYSQL数据库配置安装、重置密码以及工具连接

    一.下载mysql安装包 下载地址:https://dev.mysql.com/downloads/mysql/ 下载解压好之后,就是一个文件夹的形式. 二.配置环境变量 环境变量的配置,就是把MyS ...

  6. 在Vue中使用iview的Select控件实现一个多级选项列表

    前言 今天项目要实现一个多级选项列表,发现iview官网上没有写这个例子,于是自己就实现了,如果对你有帮助请点个赞 ‘ * ’!! 解决方法:下面我们就来使用V-for 来定义一个二级选项列表 ,代码 ...

  7. 自定义View实战

    PS:上一篇从0开始学自定义View有博友给我留言说要看实战,今天我特意写了几个例子,供大家参考,所画的图案加上动画看着确实让人舒服,喜欢的博友可以直接拿到自己的项目中去使用,由于我这个写的是demo ...

  8. Spring Boot入门系列(十三)如何实现事务

    前面介绍了Spring Boot 中的整合Mybatis并实现增删改查.不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1 ...

  9. JAVA设计模式之工厂系列(factory)

    任何可以产生对象的方法或者类,都可以称之为工厂.单例就是所谓的静态工厂. 为什么jdk中有了new,还需要工厂呢? a.灵活的控制生产过程 b.给对象加修饰.或者给对象加访问权限,或者能够在对象生产过 ...

  10. Spring官网阅读(九)Spring中Bean的生命周期(上)

    文章目录 生命周期回调 1.Bean初始化回调 2.Bean销毁回调 3.配置默认的初始化及销毁方法 4.执行顺序 5.容器启动或停止回调 Lifecycle 接口 LifecycleProcesso ...