用法

  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的核心是在一个共享代码(特别指单实例中的成员变量)中实现线程安全,方法是使得每一个线程具有其独特的副本,当线程跑到该资源代码的时候,获取自己的副本,怎么获取呢?  

  1. 首先是获取线程实例,也就是自己,然后通过线程实例获取资源,即使Thread类的使用并非是共享方法(上面的例子很好说明了是线程特有的),但实际语义是一样的,其语义核心代码是这句:Thread t = Thread.currentThread(); 没有这句代码,就没有共享线程安全。
  2. 不同点在于是把变量作为线程成员变量存放还是在堆中的共享变量存放,而后者还有更多问题无法解决,这里就不提了。

避坑:

  说到线程安全,我相信大家接触的比较多的就是各种锁了,那么如何才能做到不使用锁而线程也安全呢?没办法,如果非要竞争资源那是真没办法,所以最好就是不需要竞争资源了,而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的更多相关文章

  1. Java线程和多线程(七)——ThreadLocal

    Java中的ThreadLocal是用来创建线程本地变量用的.我们都知道,访问某个对象的所有线程都是能够共享对象的状态的,所以这个对象状态就不是线程安全的.开发者可以通过使用同步来保证线程安全,但是如 ...

  2. Java线程变量问题-ThreadLocal

    关于Java线程问题,在博客上看到一篇文章挺好的: https://blog.csdn.net/w172087242/article/details/83375022#23_ThreadLocal_1 ...

  3. Java线程并发:知识点

    Java线程并发:知识点   发布:一个对象是使它能够被当前范围之外的代码所引用: 常见形式:将对象的的引用存储到公共静态域:非私有方法中返回引用:发布内部类实例,包含引用.   逃逸:在对象尚未准备 ...

  4. Java线程的概念

    1.      计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓 ...

  5. 【转载】 Java线程面试题 Top 50

    Java线程面试题 Top 50 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员 的欢迎.大多数待遇丰厚的J ...

  6. java线程内存模型,线程、工作内存、主内存

    转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程 ...

  7. Java线程的5个使用技巧

    萝卜白菜各有所爱.像小编我就喜欢Java.学无止境,这也是我喜欢它的一个原因.日常工作中你所用到的工具,通常都有些你从来没有了解过的东西,比方说某个方法或者是一些有趣的用法.比如说线程.没错,就是线程 ...

  8. Java线程面试题 Top 50 (转载)

    转载自:http://www.cnblogs.com/dolphin0520/p/3958019.html 原文链接:http://www.importnew.com/12773.html   本文由 ...

  9. 50 道 Java 线程面试题(转载自牛客网)

    下面是 Java 线程相关的热门面试题,你可以用它来好好准备面试. 1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理 ...

  10. Java线程面试题 Top 50

    转自:http://www.importnew.com/12773.html 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java ...

随机推荐

  1. java.lang.NumberFormatException: multiple points错误问题

    最近项目一直会出现时间转换报错,一直不知道是什么问题??? java.lang.NumberFormatException: multiple points     at sun.misc.Float ...

  2. Cocos Creator脚本开发事例

    HelloWorld.js window.Global = { gint: 168, }; cc.Class({ extends: cc.Component, properties: { label: ...

  3. iOS:练习题中如何用技术去实现一个连线题

    一.介绍 本人做的app涉及的是教育行业,所以关于练习题的开发肯定是家常便饭.例如,选择题.填空题.连线题.判断题等,每一种题型都需要技术去实现,没啥多大难度,这里呢,就给出实现连线题的核心代码吧.过 ...

  4. centos找不到环境变量 -bash: ls: command not found

    #在系统中输入命令,报如下错误: [root@a1 work]# ll-bash: ls: command not found #昨时解决办法:export PATH=/usr/local/sbin: ...

  5. jvm理论-运行时数据区

    三大流行jvm sun HotSpot ibm j9 BEA JRockit Oracle 会基于HotSpot整合 JRockit. jvm运行时数据区 java虚拟机所管理的内存将会包括以下几个运 ...

  6. 【一步步学OpenGL 20】 -《点光源》

    教程 20 点光源 原文: http://ogldev.atspace.co.uk/www/tutorial20/tutorial20.html CSDN完整版专栏: http://blog.csdn ...

  7. manjaro折腾手记

    以前装过Arch,有点折腾,写了个hello world就卸载了.没用过AUR,甚至也没去了解. 听说manjaro继承Arch,几乎开箱即用,对硬件支持非常好,源里面的软件更新非常快.但是没有装中文 ...

  8. Linux系统查毒软件ClamAV (online)

    ClamAV是一个可用于Linux平台上的开源杀毒引擎,可检测木马.病毒.恶意软件和其他恶意的威胁. 官网:http://www.clamav.net/ 一.CentOS环境安装 # yum inst ...

  9. android sdk content loader 0%不动

    Make sure that eclipse is not active. If it is active kill eclipse from the processes tab of the tas ...

  10. curl命令例解

    curl -i --url "https://open.abc.com/ddn/purge/ItemIdReceiver" \-X "POST" \-u &qu ...