【Java线程安全】 — ThreadLocal
用法
ThreadLocal是用空间换时间来解决线程安全问题,方法是各个线程拥有自己的变量副本。
既然是涉及线程安全,必然有一个共享变量,声明一个:
public class Singleton { private Connection connection = DataSourceUtil.getConnection(); }
多线程下,上面资源非线程安全就会报错,所以我们不如让每一个线程单独拥有一个:
public class Singleton { private ThreadLocal<Connection> connection = new ThreadLocal< Connection >(){
@Override
protected Connection initialValue() {
return DataSourceUtil.getConnection();
}
}; }
以上就是TheadLocal的正确用法了,大家要知道,实现initialValue的时候,必须要返回一个新的对象;不然就没必要用ThreadLocal了。如何使用这个connection呢?
public class Singleton { private ThreadLocal<Connection> connectionSource = new ThreadLocal< Connection >(){
@Override
protected Connection initialValue() {
return DataSourceUtil.getConnection();
}
}; public void use(){
connectionSource.get().doSomething();
} }
请注意使用代码的命名,它将会让你知道实质。
实现
如果让我们手把手来做,该如何实现呢?
简单,用一个Map来维护这个线程和变量的映射关系,thread1->con1 ; thread2->con2 ; thread3->con3 ; 但这样并不好
因为这个map也需要保证线程安全了,所以,这里又有点绕回来的感觉,当然你可以用concurrentHashMap,是我用了这个map我又何必用threadlocal呢,而且还存在垃圾难回收的问题?还好,还有更好的方案。
更好的方案:让线程自己维护自己的所有ThreadLocal实例,然后在线程需要用的时候在Thread实例里面取!如何把ThreadLocal示例放到Thread里面?毕竟Thead不会为你设置一个ThreadLocal属性,而且一个属性也不够,因为一个Thread可能涉及多个ThreadLocal实例,很自然,在Thread里面放一个Map就好了,key就是没一个ThreadLocal实例;
下面为Thread类的源代码,看到这个属性 threadLocals是一个ThreadLocalMap类,但该类是ThreadLocal的内部静态类。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
先记住几个点:
1、ThreadLocal是一个类,所以使用ThreadLocal是创建一个ThreadLocal的类实例:threadLocal
2、每个Thread有个叫ThreadLocalMap的成员变量,ThreadLocalMap是ThreadLocal的静态内部类;
3、ThreadLocalMap里面是多个Entry,键是ThreadLocal实例,值是被保护的资源,因为每一个线程独特有的资源都是需要创建ThreadLocal实例的
4、每一个ThreadLocal都是弱引用实现,GC会被回收,所以不用担心内存泄露问题。
图示:
弱引用,使用弱引用是因为线程的执行实现可能很长,但其实资源是可以回收的,所以避免这类内存泄漏的问题。
/**
* 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其实和HashMap的实现是差不多的,拓展还是寻址、求模等,所以没啥特别。重点是ThreadLocalMap其实是给ThreadLocal使用的,因为最终还是学习ThreadLocal的精粹最重要,贴下ThreadLocal是如何使用ThreadLocalMap的
Set方法,注意这个this是ThreadLocal实例:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} /**
* 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;
}
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();
}
关键点:
ThrealLocal的核心是在一个共享代码(特别指单实例中的成员变量)中实现线程安全,方法是使得每一个线程具有其独特的副本,当线程跑到该资源代码的时候,获取自己的副本,怎么获取呢?
- 首先是获取线程实例,也就是自己,然后通过线程实例获取资源,即使Thread类的使用并非是共享方法(上面的例子很好说明了是线程特有的),但实际语义是一样的,其语义核心代码是这句:Thread t = Thread.currentThread(); 没有这句代码,就没有共享线程安全。
- 不同点在于是把变量作为线程成员变量存放还是在堆中的共享变量存放,而后者还有更多问题无法解决,这里就不提了。
避坑:
说到线程安全,我相信大家接触的比较多的就是各种锁了,那么如何才能做到不使用锁而线程也安全呢?没办法,如果非要竞争资源那是真没办法,所以最好就是不需要竞争资源了,而ThreadLocal就是这种想法下诞生的,是的,ThreadLocal就是让你不需要竞争资源,每个线程都分配一个只有自己可访问的局部资源。
我们知道ThreadLocal有四个方法,initialValue(),get(),set(T),remove() ,前三个好理解,最后一个方法大家注意了,也是最不可忽视的。特别当你用到线程池的时候,因为线程执行一个任务,而任务完成后,如果不做清除操作,remove(),那么下次其他事务再用到该线程访问同样资源的时候,你就很可能掉坑里去。
多Remove:
即使你考虑到了remove,也记得捕捉下异常,避免出现异常抛出导致不执行remove的问题。举个例子:如果你用了动态数据源切换,当你的线程在用某个数据源查询数据异常而无法执行remove的时候,下次某个用默认数据源的再用该线程,线程就会被绑定旧的数据源,从而导致错误。
内存溢出:
如果不用Remove()还会导致内存溢出
JVM:
其实ThrealLocal在JVM中也有用到,它就是TLAB,下面Copy一段介绍:
JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。
也就是说,Java中每个线程都会有自己的缓冲区称作TLAB(Thread-local allocation buffer),每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的锁进行同步(另一种同步方式就是强大的CAS自旋了),也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。
当然,以上的特性决定了分配内存首先是不被共享的,因此和在栈内分配内存一样,JVM要先做逃逸分析,如果是线程特有,那么就在TLAB中分配。
所以ThreadLocal其实就是一个比较简单的方法而已,没有CAS这么特别,你在工程中也能自己用上。
【Java线程安全】 — ThreadLocal的更多相关文章
- Java线程和多线程(七)——ThreadLocal
Java中的ThreadLocal是用来创建线程本地变量用的.我们都知道,访问某个对象的所有线程都是能够共享对象的状态的,所以这个对象状态就不是线程安全的.开发者可以通过使用同步来保证线程安全,但是如 ...
- Java线程变量问题-ThreadLocal
关于Java线程问题,在博客上看到一篇文章挺好的: https://blog.csdn.net/w172087242/article/details/83375022#23_ThreadLocal_1 ...
- Java线程并发:知识点
Java线程并发:知识点 发布:一个对象是使它能够被当前范围之外的代码所引用: 常见形式:将对象的的引用存储到公共静态域:非私有方法中返回引用:发布内部类实例,包含引用. 逃逸:在对象尚未准备 ...
- Java线程的概念
1. 计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓 ...
- 【转载】 Java线程面试题 Top 50
Java线程面试题 Top 50 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员 的欢迎.大多数待遇丰厚的J ...
- java线程内存模型,线程、工作内存、主内存
转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程 ...
- Java线程的5个使用技巧
萝卜白菜各有所爱.像小编我就喜欢Java.学无止境,这也是我喜欢它的一个原因.日常工作中你所用到的工具,通常都有些你从来没有了解过的东西,比方说某个方法或者是一些有趣的用法.比如说线程.没错,就是线程 ...
- Java线程面试题 Top 50 (转载)
转载自:http://www.cnblogs.com/dolphin0520/p/3958019.html 原文链接:http://www.importnew.com/12773.html 本文由 ...
- 50 道 Java 线程面试题(转载自牛客网)
下面是 Java 线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理 ...
- Java线程面试题 Top 50
转自:http://www.importnew.com/12773.html 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java ...
随机推荐
- 通过IntelliJ IDEA和Maven命令查看某个jar包是怎么引入的
发现打包的时候引入的jar包有几百个,如果想知道某个jar包是如何引入的,可以 看Maven Projects,点开某个Module的Dependencies 一层一层展开就可以了 可以直接输入名称高 ...
- 初识Vulkan【转】
Vulkan是Khronos组织制定的“下一代”开放的图形显示API.是与DirectX12能够匹敌的GPU API标准. Vulkan是基于AMD的Mantle API演化而来,眼下Vulkan 1 ...
- git变慢的原因
最近使用git更新代码变慢,进一步试了一下提交代码.执行git命令都很慢,换了idea的工作目录.更换git版本,所有操作都是徒劳. 最后关了火绒杀毒软件,才快了起来. 坑坑坑坑坑的火绒杀毒!浪费我至 ...
- github上总结的python资源列表【转】
Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列的资源整理.awesome-python 是 vinta 发起维护的 Python 资源列 ...
- geos 3.6.3库windows版本 已编译完成的32位版本和64位版本
网上教编译方法的很多,直接分享编译完成的很少. 我就把编译完成的分享出来吧. geos-3.6.3.tar.bz2 (Changes) 版本的 https://trac.osgeo.org/geos ...
- NoSuchMethodError: The getter 'inputs' was called on null.
I get this message : You have hit a bug in build_runner Please file an issue with reproduction steps ...
- Python学习笔录
参考:http://www.runoob.com/python3/python3-data-type.html 1. type和isinstance区别type(A()) == A, type()不会 ...
- Docker的学习
学习地址:http://blog.51cto.com/lizhenliang 和 他的视频 一 Docker 的介绍和安装 二 镜像管理 三 容器管理 四 管理应用程序数据 五 使用Docker知 ...
- 【原创 Hadoop&Spark 动手实践 1】Hadoop2.7.3 安装部署实践
目录: 第一部分:操作系统准备工作: 1. 安装部署CentOS7.3 1611 2. CentOS7软件安装(net-tools, wget, vim等) 3. 更新CentOS7的Yum源,更新软 ...
- ffmpeg中av_log的实现分析
[时间:2017-10] [状态:Open] [关键词:ffmpeg,avutil,av_log, 日志输出] 0 引言 FFmpeg的libavutil中的日志输出的接口整体比较少,但是功能还是不错 ...