【用法】
首先明确,ThreadLocal是用空间换时间来解决线程安全问题的,方法是各个线程拥有自己的变量副本。
既然如此,那么是涉及线程安全,必然有一个共享变量,我给大家声明一个:
public class Singleton { private Connection connection = DataSourceUtil.getConnection();}
不好意思,写错了,忘了是需要用线程安全的了,多线程下,上面代码必然有bug,从新写一个:
public class Singleton { private ThreadLocalconnection = new ThreadLocal< Connection >(){ @Override protected Connection initialValue() { return DataSourceUtil.getConnection(); } };}
以上就是TheadLocal的正确用法了,大家要知道,实现initialalue的时候,必须要返回一个新的对象;不然就没必要用ThreadLocal了。如何使用这个connection呢?
public class Singleton { private ThreadLocalconnectionSource = 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呢,而且还存在垃圾难回收的问题?还好,还有更好的方案。
更好的方案:依赖反转,没错,对比每个需要被保护的变量来维护映射关系,不如让thead自己把自己映射的变量值全部管起来,然后在线程需要用的时候,再取!那怎么取呢?每个使用ThreadLocal变量的地方,必然new ThreadLocal(),那么就用这个new ThreadLocal()做key好了(也就是上面的connectSource,用该命名是为了符合业务需求,透明技术实现)
先记住几个点:
1、每个Thread有个叫ThreadLocalMap的成员变量,ThreadLocalMap是ThreadLocal的静态内部类;
2、ThreadLocalMap里面是多个Entry,键是ThreadLocal实例,值是被保护的资源因为每一个线程独特有的资源都是需要创建ThreadLocal实例的()
3、每一个ThreadLocal都是弱引用实现,GC会被回收,所以不用担心内存泄露问题。
图示:
代码视角:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
弱引用,使用弱引用是因为线程的执行实现可能很长,但其实资源是可以回收的,所以避免这类内存泄漏的问题。
/** * 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> { /** 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方法:
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其实就是线程本地化对象的一个容器,注意容器这个词和其他容器相比有些不同,如Servlet容器,其本身会多少用一个实例代表(一般称为context),用代码框架管理和装载其他对象,控制着整个流程;而ThreadLocal容器只能从形态上体会,而不是从代码实现中;
ThreadLocal感悟:
说到线程安全,我相信大家接触的比较多的就是各种锁了,那么如何才能做到不使用锁而线程也安全呢?没办法,如果非要竞争资源那是真没办法,所以最好就是不需要竞争资源了,而ThreadLocal就是这种想法下诞生的,是的,ThreadLocal就是让你不需要竞争资源,每个线程都分配一个只有自己可访问的局部资源。
那既然不需要资源竞争?何来线程安全问题呢?用ThreadLocal好处在哪里?其实在Spring框架中我们能很好找到答案,SpringDao默认单例模式,但我们知道con链接是非线程安全的,而SpringDao缺能做到单例线程安全,就是因为虽然Dao是单例的,但con却是每个线程独有的,也就是大家都持有一个(具体实现这里不谈,自行复习去)。简而言之,TheadLocal就是把竞争的资源落到核心上,最小粒度上面了。所以它所谓的线程安全不在所有场景,而是某种场景才合适,而且牺牲了一定的空间代价。
我们知道ThreadLocal有四个方法,initialValue(),get(),set(T),remove() ,前三个好理解,最后一个方法大家注意了,也是最不可忽视的。特别当你用到线程池的时候,因为线程执行一个任务,而任务完成后,如果不做清除操作,remove(),那么下次其他事务再用到该线程访问同样资源的时候,你就很可能掉坑里去。即使你考虑到了remove,也记得捕捉下异常,避免出现异常抛出导致不执行remove的问题。举个例子:如果你用了动态数据源切换,当你的线程在用某个数据源查询数据异常而无法执行remove的时候,下次某个用默认数据源的再用该线程,线程就会被绑定旧的数据源,从而导致错误。
其实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这么特别,你在工程中也能自己用上。