作者:Java3y
链接:https://www.zhihu.com/question/341005993/answer/793627819
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

一、什么是ThreadLocal

声明:本文使用的是JDK 1.8

首先我们来看一下JDK的文档介绍:

/*** 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).* * <p>For example, the class below generates unique identifiers local to each* thread.* A thread's id is assigned the first time it invokes {@code ThreadId.get()}* and remains unchanged on subsequent calls.*/

结合我的总结可以这样理解:ThreadLocal提供了线程的局部变量,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离~。简要言之:往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

二、为什么要学习ThreadLocal?

从上面可以得出:ThreadLocal可以让我们拥有当前线程的变量,那这个作用有什么用呢???

2.1管理Connection

最典型的是管理数据库的Connection:当时在学JDBC的时候,为了方便操作写了一个简单数据库连接池,需要数据库连接池的理由也很简单,频繁创建和关闭Connection是一件非常耗费资源的操作,因此需要创建数据库连接池~那么,数据库连接池的连接怎么管理呢??我们交由ThreadLocal来进行管理。为什么交给它来管理呢??ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务!当时候写的代码:

public class DBUtil {//数据库连接池private static BasicDataSource source;//为不同的线程管理连接private static ThreadLocal<Connection> local;static {try {//加载配置文件Properties properties = new Properties();//获取读取流InputStream stream = DBUtil.class.getClassLoader().getResourceAsStream("连接池/config.properties");//从配置文件中读取数据properties.load(stream);//关闭流stream.close();//初始化连接池source = new BasicDataSource();//设置驱动source.setDriverClassName(properties.getProperty("driver"));//设置urlsource.setUrl(properties.getProperty("url"));//设置用户名source.setUsername(properties.getProperty("user"));//设置密码source.setPassword(properties.getProperty("pwd"));//设置初始连接数量source.setInitialSize(Integer.parseInt(properties.getProperty("initsize")));//设置最大的连接数量source.setMaxActive(Integer.parseInt(properties.getProperty("maxactive")));//设置最长的等待时间source.setMaxWait(Integer.parseInt(properties.getProperty("maxwait")));//设置最小空闲数source.setMinIdle(Integer.parseInt(properties.getProperty("minidle")));//初始化线程本地local = new ThreadLocal<>(); } catch (IOException e) {e.printStackTrace();}}public static Connection getConnection() throws SQLException {//获取Connection对象Connection connection = source.getConnection();//把Connection放进ThreadLocal里面local.set(connection);//返回Connection对象return connection;}//关闭数据库连接public static void closeConnection() {//从线程中拿到Connection对象Connection connection = local.get();try {if (connection != null) {//恢复连接为自动提交connection.setAutoCommit(true);//这里不是真的把连接关了,只是将该连接归还给连接池connection.close();//既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了local.remove();}} catch (SQLException e) {e.printStackTrace();}}
}

同样的,Hibernate对Connection的管理也是采用了相同的手法(使用ThreadLocal,当然了Hibernate的实现是更强大的)~

三、ThreadLocal实现的原理

想要更好地去理解ThreadLocal,那就得翻翻它是怎么实现的了~~~

声明:本文使用的是JDK 1.8

首先,我们来看一下ThreadLocal的set()方法,因为我们一般使用都是new完对象,就往里边set对象了

   public void set(T value) {// 得到当前线程对象Thread t = Thread.currentThread();// 这里获取ThreadLocalMapThreadLocalMap map = getMap(t);// 如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去if (map != null)map.set(this, value);elsecreateMap(t, value);}

上面有个ThreadLocalMap,我们去看看这是什么?

static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}//....很长
}

通过上面我们可以发现的是ThreadLocalMap是ThreadLocal的一个内部类。用Entry类来进行存储。我们的值都是存储到这个Map上的,key是当前ThreadLocal对象!如果该Map不存在,则初始化一个:

   void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

如果该Map存在,则从Thread中获取

   /*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param  t the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

Thread维护了ThreadLocalMap变量

   /* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null

从上面又可以看出,ThreadLocalMap是在ThreadLocal中使用内部类来编写的,但对象的引用是在Thread中!于是我们可以总结出:Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是LocalThread对象本身,value则是要存储的对象。有了上面的基础,我们看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();}

3.1ThreadLocal原理总结

  1. 每个Thread维护着一个ThreadLocalMap的引用
  2. ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
  3. 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象
  4. 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
  5. ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value

正因为这个原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响~

四、避免内存泄露

我们来看一下ThreadLocal的对象关系引用图:

ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

想要避免内存泄露就要手动remove()掉

五、总结

ThreadLocal这方面的博文真的是数不胜数,随便一搜就很多很多~站在前人的肩膀上总结了这篇博文~

最后要记住的是:ThreadLocal设计的目的就是为了能够在当前线程中有属于自己的变量,并不是为了解决并发或者共享变量的问题

有帮助?点赞!

关注我的公众号:Java3y,获取更多的原创笔记,海量视频资源/原创思维导图/学习路线

所有的文章导航:https://github.com/ZhongFuCheng3y/3y(欢迎star)

Java并发编程—ThreadLocal底层原理相关推荐

  1. Java并发编程-synchronized底层原理

    synchronized底层原理与Monitor密切相关 1.Java对象头 以 32 位虚拟机为例 普通对象 对象的类型,如Student类型,Teacher类型等是由KlassWord来表示的,它 ...

  2. 【java并发编程】底层原理——用户态和内核态的区别

    一.背景--线程状态切换的代价 java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都 ...

  3. 并发编程五:java并发线程池底层原理详解和源码分析

    文章目录 java并发线程池底层原理详解和源码分析 线程和线程池性能对比 Executors创建的三种线程池分析 自定义线程池分析 线程池源码分析 继承关系 ThreadPoolExecutor源码分 ...

  4. Java并发编程—Synchronized底层优化(偏向锁、轻量级锁)

    原文作者:Matrix海 子 原文地址:Java并发编程:Synchronized底层优化(偏向锁.轻量级锁) 目录 一.重量级锁 二.轻量级锁 三.偏向锁 四.其他优化 五.总结 一.重量级锁 上篇 ...

  5. Java并发编程—ThreadLocal用法详解

    一.用法 ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量. Threa ...

  6. Java并发编程—常见面试题

    建议: 学习java并发前需要先掌握JVM知识 关于下面问题档案的详细解析都在后面推荐的相关系列文章中 一.线程安全相关 1.什么叫线程安全? 线程安全就是说多线程访问同一代码,不会产生不确定的结果. ...

  7. Java并发编程的艺术

    1.并发编程的挑战 1.上下文切换 CPU通过给每个线程分配CPU时间片来实现多线程机制.时间片是CPU分配给各个线程的时间,这个时间非常短,一般是几十毫秒. CPU通过时间片分配算法来循环执行任务, ...

  8. 《Java并发编程的艺术》一一第2章Java并发机制的底层实现原理

    第2章Java并发机制的底层实现原理 2.1 volatile的应用 Java代码在编译后会变成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行, ...

  9. Java并发编程—定时器Timer底层原理

    原文作者:妮蔻 原文地址:Java并发编程笔记之Timer源码分析 目录 一.timer问题复现 二.Timer 实现原理分析 timer在JDK里面,是很早的一个API了.具有延时的,并具有周期性的 ...

最新文章

  1. 机器学习中的7种数据偏见
  2. 红帽加速开放混合云创新,助力企业成功迈向开源应用时代
  3. CSS单位 px pt em和rem 之间的区别
  4. 【Machine Learning 五】Coursera无法观看课程解决方案
  5. 什么是spooling技术 他有哪几部分组成_气调保鲜冷库有哪几部分组成?
  6. 【原】IOS合并lib(.a)库的终极可用方法(可用于解决duplicate symbol静态库冲突)
  7. Zabbix邮件报警设置
  8. 找出数组中第二大的值
  9. 关于启动Activity之间的及普通按钮的点击事件
  10. 东芝2000ac废粉盒怎么二次利用_嘉兴秀洲区工业垃圾怎么处理
  11. #计算机网络#学习笔记-常用端口详解
  12. Visio_Premium_project_vol版
  13. java rest 知乎_JavaWeb开发之模仿知乎首页完整代码
  14. 冬至时节饮食养生要注意“三多三少
  15. 运用fiddler工具深度配置证书抓苹果IOS微信小程序或app数据请求
  16. 计算机英语测试,计算机专业英语测试
  17. 经典共识PoW的原理及实现
  18. Altium_Designer中英文技术词汇对照
  19. galera-mariadb集群
  20. 超温及降温报警器电路设计_报警电路图讲解

热门文章

  1. Java---设计模块(单例的变形)(多例)
  2. PMAC运动程序例程(一)
  3. Zclip-jquery插件支持全浏览器复制
  4. [转]【Android】9-patch图片以及例子说明
  5. PHP 如何得到当前页面中所有已定义的变量和常量
  6. org.apache.commons.lang.StringUtils
  7. LeetCode——Kth Largest Element in an Array
  8. js数组的定义方法与基本使用
  9. 设计模式笔记之七 (桥接模式)
  10. 系统思考与《第五项修炼》