1. 是什么?

首先ThreadLocal类是一个线程数据绑定类, 有点类似于HashMap<Thread, 你的数据> (但实际上并非如此), 它所有线程共享, 但读取其中数据时又只能是获取线程自己的数据, 写入也只能给线程自己的数据

2. 怎么用?

public class ThreadLocalDemo {

	private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

	public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
threadLocal.set("zhazha" + Thread.currentThread().getName());
String s = threadLocal.get();
System.out.println("threadName = " + Thread.currentThread().getName() + " [ threadLocal = " + threadLocal + "\t data = " + s + " ]");
}, "threadName" + i).start();
}
}
}

从他的输入来看, ThreadLocal是同一个, 数据存的是线程自己的名字, 所以和threadName是一样的名称

threadName = threadName9 [ threadLocal = java.lang.ThreadLocal@43745e1f	 data = zhazhathreadName9 ]
threadName = threadName3 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName3 ]
threadName = threadName7 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName7 ]
threadName = threadName0 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName0 ]
threadName = threadName6 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName6 ]
threadName = threadName1 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName1 ]
threadName = threadName2 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName2 ]
threadName = threadName4 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName4 ]
threadName = threadName5 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName5 ]
threadName = threadName8 [ threadLocal = java.lang.ThreadLocal@43745e1f data = zhazhathreadName8 ]

3. 有什么使用场景

我们使用获取到一个保存数据库请求, tomcat会有一个线程去操作数据库保存数据和响应数据给客户, 而操作数据库需要存在一个数据库链接Connection对象, 只要是同一个数据库链接, 就可以得到同一个事务

但一个线程是如何获取同一个Connection从而获取同一个事务 ?

方法其实很简单, 使用 ThreadLocal绑定在线程中, 类似于Map<Thread, Connection>去存储

4. 底层源码分析

get方法分析

public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取ThreadLocalMap
ThreadLocal.ThreadLocalMap map = getMap(t);
// map不为null
if (map != null) {
// 根据this获取我们的entry
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果map获取为空, 则初始化
return setInitialValue();
}

根据上面源码分析发现ThreadLocal底层使用的不是类似Map<Thread, Data> 这种结构而是



每个线程都有一个属于自己的ThreadLocalMap结构

而他的结构是这样的

其中的table数组在上面的 setInitialValue() 方法创建详细源码在这

private T setInitialValue() {
// 这个方法在我们的用例中没写, 所以默认放回 null
T value = initialValue();
Thread t = Thread.currentThread();
// 获取线程单独的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果我们初始化了initialValue() 方法, 那么它默认初始化的值会被设置到这里,
// 但是实际上我们用例为null, 所以不会执行这段代码
map.set(this, value);
} else {
// 线程ThreadLocalMap 没被创建, 需要创建出来,
// 其中的 table 数组在这里被创建
createMap(t, value);
}
// 这里我没分析, 忽略了
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}

他会在ThreadLocalMap中调用构造方法初始化

// 其中 firstValue是我们的值
void createMap(Thread t, T firstValue) {
// 关注下 this , 它是ThreadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
} ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 我们的table在这里被创建, INITIAL_CAPACITY == 16
table = new Entry[INITIAL_CAPACITY];
// 获取不超过16的hashCode
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 根据计算出来的HashCode设置到对应的table数组中, 这里key是ThreadLocal, value是我们的值
table[i] = new Entry(firstKey, firstValue);
// 初始时, 已经有一个值了, 所以size = 1
size = 1;
// 设置扩容阈值加载因子 threshold = len * 2 / 3; 默认为长度的三分之二
setThreshold(INITIAL_CAPACITY);
}

从这段代码可以发现, firstKey其实是我们ThreadLocalMap中的key, 而firstKey就是我们的ThreadLocal, 而value就是我们 initialValue() 方法返回的值, 这里默认为null, 所以我们可以得出这样一幅图

总结下

每个线程都有一个属于自己的ThreadLocalMap类, 他用于关联多个以ThreadLocal对象为key, 以你的数据valueEntry对象, 且该对象的key是一个弱引用对象

接下来我们分析下这个类Entry, 它继承了弱引用类WeakReference

static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; Entry(ThreadLocal<?> k, Object v) {
// ThreadLocal被设置为弱引用
super(k);
// 保存value
value = v;
}
}

发现 ThreadLocal 被设置为弱引用

存在什么问题?

为什么前面的Entry需要继承弱引用类WeakReference呢?

首先了解下什么是引用

简单了解下强、软、弱和虚引用

  • 强引用: 如果引用变量没被指向null则, 引用对象将被停留在堆中, 无法被虚拟机回收Object obj = new Object()
  • 软引用: 如果虚拟机堆内存不够用了(在发生内存溢出之前), 虚拟机可以选择回收软引用对象, 虚拟机提供SoftReference类实现软引用, 一般用于相对比较重要但又可以不用的对象, 比如: 缓存
  • 弱引用: 生于系统回收之前, 死于系统回收完毕之后, 弱引用需要依附于强引用或者软引用才能够防止被虚拟机回收, 比如放到一个引用队列(ReferenceQueue)中或者对象中, 比如: ThreadLocalMapEntry对象, 需要依附于ThreadLocal才能够不被删除掉
  • 虚引用: 可以理解为跟强引用对象没了引用变量一样, 随时可以被回收, 只要依附于引用队列中才不会被回收, 通常用于网络通讯的NIO上, 用于引用直接内存, java提供类PhantomReference来实现虚引用

为何Entry对象需要为弱引用?

答案很明显, 防止内存泄漏[1], 我们来详细分析分析

首先, 我们知道ThreadLocalMap中存放的是一个一个Entry对象, 而 Entry对象中的key(ThreadLocal)被设计成弱引用如果key被设置成null

(比如: 外部的测试用例中的private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();这个对象被设置为 threadLocal = null) 则, 你会发现此时Entry的对象key = null value = xxxx(此时这个Entry实质上是没有用的, 连key都给设置成null, 它的value还有什么用?) 而ThreadLocalMap中存储的还是Entry对象的地址, 此Entry不会被回收, 但Entry对象的key被设置成弱引用, 就不一样了, 直接会被回收掉它

[1]内存泄漏: 程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

那么这样就没有问题了么???(打脸篇)

再次强调, 下面这段话别信, 仔细看到最后, 你会发现这被打脸了

其实应该是没什么问题了(被自己打脸了, 别信这句话), 只不过很多网友觉得Entry中的key虽然是弱引用, 但Entry可能不会被回收, 因为entryvalue是强引用, 可能导致线程下的entry无法被回收掉, 最好推荐使用threadLocal.remove方法删除掉, 前面说的threadLocal = null方法不推荐使用, 那么为了以防万一吧, 还是手动调用下remove方法比较好一点

下面是我对threadLocal = null方式的代码测试:

public class ThreadLocalDemo {

	private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>() {
@Override
protected String initialValue() {
return "1";
} @Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("threadLocal1被回收");
}
};
private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>() {
@Override
protected String initialValue() {
return "2";
} @Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("threadLocal1被回收");
}
}; public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException {
// 获取ThreadLocalMap
Thread thread = Thread.currentThread();
Class<? extends Thread> clazz = thread.getClass();
Field threadLocals = clazz.getDeclaredField("threadLocals");
threadLocals.setAccessible(true);
Object threadLocalsObj = threadLocals.get(thread);
// 获取ThreadLocalMap下的table数组
Class<?> threadLocalsMapClass = threadLocalsObj.getClass();
Field tableField = threadLocalsMapClass.getDeclaredField("table");
tableField.setAccessible(true);
Object[] tableObj = (Object[]) tableField.get(threadLocalsObj);
threadLocal1.set("zhazha");
threadLocal2.set("xixi");
System.out.println(threadLocal1.get());
System.out.println(threadLocal2.get());
// 在这里下一个断点看看ThreadLocal被回收, Entry是否被回收
threadLocal1 = null;
threadLocal2 = null;
System.gc();
Thread.sleep(5000);
System.out.println(tableObj);
System.out.println("主线程结束");
}
}

输出是这样的:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by com.zhazha.threadlocal.ThreadLocalDemo (file:/D:/program/codes/java/Concurrentcy/reviewjuc/target/classes/) to field java.lang.Thread.threadLocals
WARNING: Please consider reporting this to the maintainers of com.zhazha.threadlocal.ThreadLocalDemo
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
zhazha
xixi
[Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;@aecb35a
主线程结束

如果上面的代码不调用gc方法, 很长一段时间内不会被回收, 应该是jvm gc还没开始被动回收

但!!!但!!!但!!! 看调试代码

数组中的referent字段还是存在的, 下图是gc回收之前查看数组中的元素发现, 字段referent(也就是ThreadLocal) 它还在

gc方法执行完毕后, referent被回收掉了, referent = null

但是那个对象怎么回事??? 没被回收掉?? 打脸了??? 求助广大网友给我看看

那让我们试试 remove方法试试?

好了, 直接没了, 找不到那两个属性了

An illegal reflective access operation has occurred这个问题怎么帮? 这回真不知道了, 应该不影响我们的代码么?

算了为了把这个红色的字改没掉, 改了改源码

public class ThreadLocalDemo {

	private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>() {
@Override
protected String initialValue() {
return "1";
} @Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("threadLocal1被回收");
}
};
private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>() {
@Override
protected String initialValue() {
return "2";
} @Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("threadLocal1被回收");
}
}; private static Unsafe unsafe; static {
Class<Unsafe> unsafeClass = Unsafe.class;
Unsafe unsafe = null;
try {
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
ThreadLocalDemo.unsafe = (Unsafe) unsafeField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
} public static void main(String[] args) throws InterruptedException, NoSuchFieldException {
Thread thread = Thread.currentThread();
long threadLocalsFieldOffset = unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals"));
Object threadLocalMapObj = unsafe.getObject(thread, threadLocalsFieldOffset);
long tableOffset = unsafe.objectFieldOffset(threadLocalMapObj.getClass().getDeclaredField("table"));
Object tableObj = unsafe.getObject(threadLocalMapObj, tableOffset);
threadLocal1.set("zhazha");
threadLocal2.set("xixi");
System.out.println(threadLocal1.get());
System.out.println(threadLocal2.get());
threadLocal1 = null;
threadLocal2 = null;
// threadLocal1.remove();
// threadLocal2.remove();
System.gc();
System.out.println(tableObj);
System.out.println("主线程结束");
}
}

好了没这个问题了

zhazha
xixi
threadLocal1被回收
threadLocal1被回收
[Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;@7dc222ae
主线程结束
与目标VM断开连接, 地址为: ''127.0.0.1:58958',传输: '套接字'', 传输: '{1}' 进程已结束,退出代码0

ThreadLocal底层原理学习的更多相关文章

  1. Activiti工作流学习笔记(三)——自动生成28张数据库表的底层原理分析

    原创/朱季谦 我接触工作流引擎Activiti已有两年之久,但一直都只限于熟悉其各类API的使用,对底层的实现,则存在较大的盲区. Activiti这个开源框架在设计上,其实存在不少值得学习和思考的地 ...

  2. ThreadLocal的原理和在框架中的应用

    ThreadLocal的原理和在框架中的应用 博客分类: java基础 框架多线程SpringthreadDAO  概述      我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久 ...

  3. Neo4j图数据库简介和底层原理

    现实中很多数据都是用图来表达的,比如社交网络中人与人的关系.地图数据.或是基因信息等等.RDBMS并不适合表达这类数据,而且由于海量数据的存在,让其显得捉襟见肘.NoSQL数据库的兴起,很好地解决了海 ...

  4. 《React Native 精解与实战》书籍连载「React Native 底层原理」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  5. 【Socket】linux黑客之网络嗅探底层原理

      1.mystery引入 1)网络嗅探属于网络攻防类的安全软件,其基于原始套接字技术开发的 2)原始套接字是一种套接字底层技术,它工作在网络层 3)谈到网络安全,刚好本学期学过这门课程,这里myst ...

  6. PHP5底层原理之垃圾回收机制

    概念 垃圾回收机制 是一种内存动态分配的方案,它会自动释放程序不再使用的已分配的内存块. 垃圾回收机制 可以让程序员不必过分关心程序内存分配,从而将更多的精力投入到业务逻辑. 与之相关的一个概念,内存 ...

  7. Java面试底层原理

    面试发现经常有些重复的面试问题,自己也应该学会记录下来,最好自己能做成笔记,在下一次面的时候说得有条不紊,深入具体,面试官想必也很开心.以下是我个人总结,请参考: HashSet底层原理:(问了大几率 ...

  8. ThreadLocal的原理与使用

    前言 在java web项目中,经常会使用到单例对象,从服务器启动那一时刻就实例化全局对象.然后会对某些全局对象的属性进行修改之类的操作,但是我们知道项目一般都是部署到tomcat.Jboss之类的服 ...

  9. Android进程永生技术终极揭秘:进程被杀底层原理、APP应对技巧

    1.引言 上个月在知乎上发表的由“袁辉辉”分享的关于TIM进程永生方面的文章(即时通讯网重新整理后的标题是:<史上最强Android保活思路:深入剖析腾讯TIM的进程永生技术>),短时间内 ...

随机推荐

  1. python基础之常用模块一(sys、greenlet、pymysql、paramiko、pexpect、configparser)

    一.sys模块(内置模块) 用于提供对解释器相关的操作 import syssys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0) ...

  2. mysql基础之数据库变量(参数)管理

    数据库的数据存放路径:[root@ren7 mysql]# pwd /var/lib/mysql [root@ren7 mysql]# ls aria_log.00000001 ibdata1 mul ...

  3. 第35章-CentOS7实战

    补充安装软件包 yum -y install vim lrzsz bash-completion telnet nmap 关闭selinux:/etc/selinux/config 关闭防火墙:sys ...

  4. Python数模笔记-Scipy库(1)线性规划问题

    1.最优化问题建模 最优化问题的三要素是决策变量.目标函数和约束条件. (1)分析影响结果的因素是什么,确定决策变量 (2)决策变量与优化目标的关系是什么,确定目标函数 (3)决策变量所受的限制条件是 ...

  5. Scrapy爬虫返回302重定向问题解决方法

    scrapy爬虫遇到爬取页面时302重定向导致response页面与实际需要爬取的页面信息不一致,导致无法正常获取信息,查看日志存在 scrapy.downloadermiddlewares.redi ...

  6. Docker学习(15) Docker容器的跨主机连接

    Docker容器的跨主机连接 Docker使用网桥跨主机容器连接 Docker使用Open cSwitch实现跨主机容器连接 Docker使用weave实现跨主机容器连接

  7. Python+Selenium+Appium+API学习使用过的命令

    adb devices 查看连接电脑的手机设备 weditor 启动uiautomatorviewer2元素定位工具 以下2个命令作用一样 adb shell dumpsys activity | f ...

  8. DDD中聚合、聚合根的含义以及作用

    聚合与聚合根的含义 聚合: 聚合往往是一些实体为了某项业务而聚类在一起形成的集合 , 举个例子, 社会是由一个个的个体组成的,象征着我们每一个人.随着社会的发展,慢慢出现了社团.机构.部门等组织,我们 ...

  9. 使用shell脚本循环处理文本

    公司是使用puppet来进行配置管理, 某天修改完puppet后领导回复: 我们有一个文档cabinet.txt记录了物理机器所在的机柜, 除了文档里的其他机器都是虚拟机或云服务器, 对虚拟机的pup ...

  10. TensorFlow文本情感分析实现

    TensorFlow文本情感分析实现 前面介绍了如何将卷积网络应用于图像.本文将把相似的想法应用于文本. 文本和图像有什么共同之处?乍一看很少.但是,如果将句子或文档表示为矩阵,则该矩阵与其中每个单元 ...