ThreadLocal示例与原理
多个线程间共享变量,可能会造成线程不安全的问题,需要加锁来实现线程安全,但是加锁会降低系统的吞吐量
但是有些变量就不需要线程间共享。比如数据库连接池里的连接,我们可以通过串行线程封闭技术来安全的使用连接池中的连接。一个线程A从连接池中把连接拿走,连接池保证不把该连接给别的线程,线程A同样不会把连接发布出去,用完之后返回给连接池,这样一个连接总是在一个线程中使用,不会同时被两个线程操作。线程A保存数据库连接就可以使用 ThreadLocal 来保存,可以在多个方法中获取操作数据库,用完删除即可。(生产者和消费者模式也是使用串行线程封闭技术,大家可以考虑下)
ThreadLocal 里的数据,其它线程无法访问,只要使用者不把数据发布出去,就可以安全操作它们。我们来看看如何一个 demo 来看下 ThreadLocal 如何使用:
ThreadLocal与Thread如何绑定
上文我说过 ThreadLocal 会与它所属的 Thread 绑定,这个绑定是什么意思呢,下面我们来看看 Thread的一处源码:
1 | /* 与此线程相关的ThreadLocal值.这个映射由ThreadLocal类维护 */ |
ThreadLocal.ThreadLocalMap 类型的 threadLocals属性是保存与当前线程相关的ThreadLocal实例,该map 由ThreadLocal来维护.下面我来看看ThreadLocalMap到底是个什么?
ThreadLocalMap 是一个定制的散列映射,只适用于维护线程本地值.没有任何操作被导出到ThreadLocal类之外.类是包私有的,允许在类线程中声明字段.为了帮助处理非常大且长期存在的用法,哈希表条目对键使用WeakReference.但是由于没有使用引用队列,所以只有在表空间不足时才会删除陈旧的条目
1 | static class Entry extends WeakReference<ThreadLocal<?>> { |
看了源码可知:ThreadLocalMap是以ThreadLocal实例为健,用户要线程私有化的数据为值的散列表,并且健;还是弱引用类型的
下面我们来讲下ThreadLocal如何与线程关联起来的.ThreadLocal实例在调用set和get的时候,会先获取当前线程的threadLocals属性,判断threadLocals属性是否为空,若不为空则进行获取或者添加操作,否则会创建一个ThreadLocalMap实例赋给当前线程的属性threadLocals;然后往里put一个键值对,当get或set方法时健都是当前ThreadLocal实例,只不过是get时,值为ThreadLocal中initValue方法返回的值,默认为null;方法为set时,则为调用者传进的实参
1 | /** |
set
1 | /** |
下面我们用一张图来概括下线程,线程私有变量以及用户定义的数据之间的关系,加深我们的理解:
上图中Entry中的key是弱引用类型的,因此用户程序使用完ThreadLocal对象之后忘记调用remove方法,下一次GC会把只有一个弱引用的ThreadLocal回收掉,此时key指向null,则无论谁都不能访问到该key对应的value对象,只要线程实例不退出就无法释放,如果value对象占用内存很大,则可能会造成OOM.但是ThreadLocalMap底层会对key为null的value进行清理