一,先来举个栗子直观的感受下这个JAVA面试的常客
运行结果:
如图1所示,在上述示例中第一次得到()取出的值为初始化的“皮皮虾”,再第二次设定后,获取到的值为“皮皮虾,我们走”,说明设置方法会覆盖原先的值。
2,初值方法:返回线程本地变量的当前线程的初始值可以避免在没有第一次调用GET方法出现空的情况设置之后的。
二,初探ThreadLoal
1,查看源码释义
大致的意思是:
ThreadLocal提供了线程本地变量,它与普通变量不同之处在于:每个线程访问该变量的线程(通过get / set方法),都会出初始化一个完全独立的副本。该实例通常用private static修饰并将线程与状态关联。
2,常用阿比说明:
来看下设置方法的源码
的的GetMap操作方法:
这里的threalLocals是什么呢?
实际上就是一个ThreadLocalMap,而这个ThreadLocalMap是的ThreadLocal的中的内部类
ThreadLocalMap会初始化一个大小为16的条目对象,条目继承了的的的WeakReference(这里在后面说明下会关系到强引用/弱引用和垃圾回收的关系).Entry对象维护KV键值对的关系,需要注意的是这里ķ永远都是的ThreadLocal中的对象。
我们继续看ThreadMap中的设置方法
这里会根据ThreadLocal的的的对象的哈希码值计算出位置I,定位到标签[I]位置的条目,如果该位置条目中的密钥和即将设置的密钥相等,则将该位置的值设置为最新的值;如果该位置条目中的密钥和即将设置的密钥不相等,则将该位置的值设置为最新的值;如果该位置条目中的密钥为空,则初始化一个条目对象在该位置,存储着将要设置的KV;如果该位置的条目对象中的重点和即将设置的键没有关系,则会遍历寻找下一个位置重复上述过程.ThreadLocalMap是一个定制的的HashMap中中,自然具有自动扩容的特性。
这里理解了一套方法,获取方法其实也就类似了。
三,ThredLocal的内存泄漏问题
3.1 JAVA中的四种引用
3.1.1强引用
强引用一般是我们最熟悉的,比如:Object obj = new Objetc(),new关键字创建出来的对象obj就是强引用。即使JVM会抛出OOM,垃圾回收器不会回收这些对象。如果想中断某个对象的强引用关系,可以通过显示的将引用赋值为空来实现GC的作用。比如向量类载体的去除方法就是通过这样实现GC工作的
3.1.2软引用
如果对象具有软引用关系,在内存空间足够的情况下,垃圾回收器就不回收它,当内存空间不足时,这部分对象就会被回收。
示列:
运行结果:
实例对象有两条引用路径:。一个是实例变量的强引用,一个是来自java.lang.ref.SoftReference的软引用如果后续进行实例= NULL,则Intance则变成了软引用对象这个时候垃圾回收器不一定会立即回收该对象,但是会在抛出的OutOfMemoryError异常异常异常之前回收这部分对象。
3.1.3弱引用
示例:
运行结果:
在的的的Java中用java.lang.ref.WeakReference来表示的,当JVM进行垃圾回收时,不论内存是否足够都会回收被若引用关联的对象。
3.1.4虚引用
虚引用并不影响对象的生命周期。在java的的的中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。必须和的的ReferenceQueue上引用队列关联使用。软引用和若引用也可以和引用队列联合使用,如果它们引用的对象被回收,则会加入引用队列中.ReferenceQueue提供了民意调查方法可以判断还有哪些软引用对象没有被清除,然后进行明确。
运行结果:
3.2内存泄漏问题
前面已经提到的ThreadLocal中的提供了线程本地变量,真正存放KV的是在ThreadLocalMap中,从上面的图中看以看出ThreadLocalMap内部置一个条目数组,这个条目数组继承了的了WeakReference的,其中ķ传入到了的了的WeakReference中,ThreadLocalMap里面的键为的ThreadLocal的中的对象的弱引用。
如果当前线程一直存在而没有调用的ThreadLocal的的的的,并且这时候其它地方还是有对的的的ThreadLocal的的引用,则当前线程的ThreadLocalMap变量里面会存在的的ThreadLocal的的变量的引用和值对象的引用是不会被释放的,这就会造成内存泄露的。
如果ThreadLocal中的中的变量没有了外部的强引用并且当前线程还存在的情况下,由于线程的ThreadLocalMap里面的ķ键是弱引用,则当前线程的ThreadLocalMap中ķ键的弱引用会被在GC的时候回收,但是这个时候还是可能会发生内存泄漏,ThreadLocalMap中的键为空,但是值对象却不为空,也就是存在键为空,值非空的输入条目。其实在的的的ThreadLocal中的设置和获取和删除API内部通过调用expungeStaleEntry方法对这些键为空的条目进行清理。
不多说上源码来看的ThreadLocal中的本身提供的删除方法:
根据ThreadLocal的的的对象的哈希码值计算出位置I,定位到标签[I]位置的条目,如果条目中的密钥和当前的的ThreadLocal的的相等,则调用java.lang.ref.Reference中中中中的明确()方法清除对键的弱引用,而后调用expungeStaleEntry方法对值强引用进行清除从下图可以看出是通过显示赋零值来达到下次GC回收的目的。
expungeStaleEntry方法的主要作用是:
通过rehase位于已知键为空的位置staleSlot和在staleSlot之间键为空位置时隙之间任何可能冲突的条目来清除陈旧的条目因此在使用的ThreadLocal的时最后请不要忘记调用threadlocal.remove() ;
四,使用场景
1,的ThreadLocal的并不解决多线程共享变量的问题,因为变量并不共享
2,实例需要在多个方法中共享,但不希望被多个线程共享
3,网络应用中存放用户信息,会议,数据库连接的管理
如图4所示,每个线程需要有自己单独的实例
参考文章:
http://www.jasongj.com/java/threadlocal/
https://www.cnblogs.com/huajiezh/p/5835618.html
https://www.cnblogs.com/dolphin0520/p/3920407.html
http://ifeve.com/%E4%BD%BF%E7%94%A8threadlocal%E4%B8%8D%E5%BD%93%E5%8F%AF%E8%83%BD%E4%BC%9A %E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2 /#更-36646
JAVA并发编程的艺术,爪哇并发实践
CSDN文章同步会慢些,欢迎关注微信公众号