深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类
ThreadLocal,很多人都叫它做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。
可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那样每个线程可以访问自己内部的副本变量。
这句话从表面上看起来理解正确,但实际上这种理解是不太正确的。下面我们细细道来。
多线程并发执行时,需要数据共享,因此才有了volatile变量解决 多线程间的数据可见性,
也有了锁的同步机制,使变量或代码块在某一时该,只能被一个线程访问,确保共享数据的正确性。(Synchronized用于线程间的数据共享的)
多线程并发执行时,并不是所有数据都需要共享的,这些不需要共享的数据,让每个线程去维护就OK了,ThreadLocal就是用于线程间的数据隔离的。
深入解析ThreadLocal类:
先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。
先看下get方法的实现:
第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。
然后接着下面获取到Entry键值对,注意这里获取Entry时参数传进去的是 this,即ThreadLocal实例,而不是当前线程t。如果获取成功,则返回value值。
如果map为空,则调用setInitialValue方法返回value。
接着看一下getMap方法中做了什么:
在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals,类型为ThreadLocalMap。
这里意味着每一个线程都自带一个ThreadLocalMap成员变量。
继续取看ThreadLocalMap的实现:
可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。
也就是说WeakReference封装了ThreadLocal,并作为了ThreadLocalMap的Entry的Key。
总结一下,在每个线程Thread内部有一个ThreadLocalMap类型的成员变量threadLocals,
这个ThreadLocalMap成员变量的Entry的Key为,当前ThreadLocal变量的WeakReference封装,value为变量。
为何ThreadLocalMap的键值为ThreadLocal对象? 因为每个线程中可能需要有多个threadLocal变量,也就是ThreadLocalMap里面可能会有多个Entry。
在每个线程内部 第一次调用ThreadLocal.get方法时,都会返回Null。因为默认情况下,initialValue方法返回的是null。
null 赋给(强转) 基本数据类型时会抛的空指针,null赋给 引用类型没问题。
可以在ThreadLocal的构造函数重写initialValue()方法。如下
ThreadLocal<Long> longLocal =
new
ThreadLocal<Long>(){
protected
Long initialValue() {
return
Thread.currentThread().getId();
};
};
或者在调用ThreadLocal.get方法之前,需要先执行set(),以保证threadlocals中有值。
或者value为引用类型变量,null赋给 引用类型没问题。,如下,hibernate中典型的ThreadLocal的应用:
- private static final ThreadLocal threadSession = new ThreadLocal();
- public static Session getSession() throws InfrastructureException {
- Session s = (Session) threadSession.get();
- try {
- if (s == null) {
- s = getSessionFactory().openSession(); //分发一个实例 的内存地址,一般就是new一个实例了
- threadSession.set(s);
- }
- } catch (HibernateException ex) {
- throw new InfrastructureException(ex);
- }
- return s;
- }
开篇说ThreadLocal创建副本 的说法是不太正确的。为什么?
从上面这个hibernate的例子来看,这是一个使用ThreadLocal解决数据库连接的单例 在多线程中同时操作查询和关闭的情况。
首先这里面不是创建副本,而是分发新的内存地址(即,新的数据库连接的单例的内存地址),以当前ThreadLocal为key,value指向传入新的数据库连接的单例的内存地址。
从而达到单个线程获取数据连接的线程安全而已,也就是每个线程都有一个独立的数据库连接的单例。
假设相反情况,一个数据库连接单例 如果在2个线程中被同时引用,2线程分别同一时间操作读取和close,肯定会出现冲突。
所以需要减少每次new的开销还是得使用数据库连接池。
ThreadLocal的内存泄露问题:
当使用线程池来复用线程时,一个线程使用完后并不会销毁线程,那么 分发的那个实例会一直绑定在这个线程上。
由于WeakReference封装了ThreadLocal,并作为了ThreadLocalMap的Entry的Key。如果在某些时候ThreadLocal对象被赋Null的话,弱引用会被GC收集,这样就会导致Entry的Value对象找不到,
线程被复用后如果有调用ThreadLocal.get/set方法的话,方法里面会去做遍历清除 以[ThreadLocal=Null ]为Key的Entry; 但如果一直没调用ThreadLocal.get/set方法的话就会导致内存泄漏了。
所以一般线程用完ThreadLocal后,要调用threadLocal.remove(); 如下
- public static void close() {
- // 获取当前线程内共享的Connection
- Connection conn = threadLocal.get();
- try {
- // 判断是否已经关闭
- if(conn != null && !conn.isClosed()) {
- // 关闭资源
- conn.close();
- // 移除Connection
- threadLocal.remove();
- conn = null;
- }
- } catch (SQLException e) {
- // 异常处理
- }
- }
深入理解java:2.4. 线程本地变量 java.lang.ThreadLocal类的更多相关文章
- 线程本地变量ThreadLocal源码解读
一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...
- linux TLS 线程本地变量
最近在写底层hook的时候, 涉及到线程安全问题, 最开始我设计的时候使用的互斥量, 但是考虑到都是底层函数,加锁会导致性能问题, 一直在思考优化方案, 后来偶然想到,java里面有线程本地变量的AP ...
- .Net学习难点讨论系列17 - 线程本地变量的使用
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- .Net - 线程本地变量(存储)的使用
关于C#多线程的文章,大部分都在讨论线程的开始与停止或者是多线程同步问题.多线程同步就是在不同线程中访问同一个变量或共享资源,众所周知在不使用线程同步的机制下,由于竞争的存在会使某些线程产生脏读或者是 ...
- Threadlocal线程本地变量理解
转载:https://www.cnblogs.com/chengxiao/p/6152824.html 总结: 作用:ThreadLocal 线程本地变量,可用于分布式项目的日志追踪 用法:在切面中生 ...
- 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理
前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...
- java线程本地变量
ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名为Thre ...
- Java并发机制(4)--ThreadLocal线程本地变量(转)
个人理解: 说明:看了博客园中大神写的ThreadLocal的详解,感觉还是有些迷糊,下面用自己的理解简单描述下ThreadLocal的机制(难免有误): 1.首先ThreadLocal用于存储对应线 ...
- 线程本地变量ThreadLocal
一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...
随机推荐
- IView 给Submenu增加click事件失效解决方案
在浏览器中,打开开发者选项(F12) 找出对应的class,给其添加一个点击事件,就可以了. 具体的 document 操作,看这里 ----> https://www.cnblogs.co ...
- shell变量、函数和数组以及字符串的截取
一.变量 1.shell变量名 (1)可以由字母.数字.下划线等字符组成.但是第一个字符必须是字母或者下划线. (2)若果变量中包含下划线(_)则要特别注意,$project_svn_$date.ta ...
- Acwing-281-硬币(背包)
链接: https://www.acwing.com/problem/content/283/ 题意: 给定N种硬币,其中第 i 种硬币的面值为Ai,共有Ci个. 从中选出若干个硬币,把面值相加,若结 ...
- 织梦dedecms做的网站首页标题篡改跳转赌博网站解决方案
织梦dedecms因其强大功能,简单实用的优点常常被用来做企业网站,程序开源使用的人多了网站漏洞多会有中毒的情况,常见的有一种,首页标题关键词描述被篡改,百度快照收录点击后跳转的赌博网站,怎么解决这个 ...
- MaxCompute - ODPS重装上阵 第六弹 - User Defined Type
MaxCompute(原ODPS)是阿里云自主研发的具有业界领先水平的分布式大数据处理平台, 尤其在集团内部得到广泛应用,支撑了多个BU的核心业务. MaxCompute除了持续优化性能外,也致力于提 ...
- 【Vue】input textarea自动滚动到输入处
由于我这里要把接口返回的日志不断地新增到textarea里,想实现自动滚动日志的效果. 1.首先定一个textarea类型的input组件 <el-input id="textarea ...
- openwrt boot 启动出现的问题
一.boot启动出现JFFS2挂载文件系统错误 问题排查: 1.固件问题. 2.刷机,写进去不完整. 3.flash有问题. 二.openwrt 进入web页面出错 解决方法: 1.SSH进去,先恢复 ...
- 在win7下,将QT集成到vs2010上
在网上查了很多,自己先是下载了一个5.2.0版本的,但在我的电脑上运行时老报错,一怒之下决定不再使用5.2.0版本的QT,而先择了更低版本的4.8.5版本,然后.....然后就成功了.谢天谢地,在这我 ...
- 黑马lavarel教程---6、简单验证
黑马lavarel教程---6.简单验证 一.总结 一句话总结: 1.验证的最简单实例:$request的validate方法,验证通过可以继续进行,验证失败就重定向 2.中文提示可以用中文语言包 3 ...
- GreyMagic
hearthbuddy中的一段代码 // Token: 0x06001A79 RID: 6777 RVA: 0x000DD024 File Offset: 0x000DB224 internal In ...