上一节你弄懂了ThreadLocal是什么、它的根本应用形式、get办法的底层原理。这一节让持续深入研究下:
- ThreadLocal的set源码原理
- JVM的中的强援用、弱援用、软援用、虚援用
- 弱援用在ThreadLocal的利用
- ThreadLocal内存透露问题剖析
- ThreadLocal利用场景举例
ThreadLocal set办法源码原理
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">ThreadLocal set办法源码原理</span></h3></div>
你有了浏览threadLocal的get办法的教训,set办法的源码会变得非常简单。set源码如下所示:
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办法调用的setInitialValue简直截然不同,只是没有了initialValue()办法而已。
如果以后线程第一次应用threadLcoal.set(Obejct),(假如以后线程之前也没有调用过get办法),就会创立一个默认大小为16的threadLocalMap,并且将key设为threadLocal对象,value设置为对应的某个Object。
如果是第二次set肯走的是map.set(this, value);这句话的分支,间接向以后线程的threadLocalMap中设置一个key-value对。
如下图所示:
JVM中的强援用、弱援用、软援用、虚援用基础知识
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">JVM中的强援用、弱援用、软援用、虚援用基础知识</span></h3></div>
你还记得ThreadLocalMap这个每个Thread都有的本地变量吗?这个Map中的外围的数据结构是一个Entry,代表了Key-Value对的数据,Key值是ThreadLocal对象,value是存储的对象数据。代码如下所示:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
这个Entry继承了一个WeakReference的对象。如果相熟JVM的同学,可能理解这个对象是什么,它被称作弱援用。
在java中,对象援用能够强援用、软援用、弱援用、虚援用四种,是jvm回收内存判断的重要规范之一。上面我简略给大家介绍下他们是什么,个别利用在什么场景。
强援用StrongReference,个别申明的一个对象,都是强援用。应用场景,比方 Loan l = new Loan(); l就是一个强援用。gc如果发现一个对象被强援用指向,如果JVM空间有余的时候,就算OOM也不会回收它。
软援用SoftReference,当JVM空间不够的时候,gc会先回收软援用的空间。应用场景:适宜用于缓存。
举个例子:Andriod用Map缓存位图数据。
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();public void addBitmapToCache(String path) { // 强援用的Bitmap对象 Bitmap bitmap = BitmapFactory.decodeFile(path); // 软援用的Bitmap对象 SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap); // 增加该对象到Map中使其缓存 imageCache.put(path, softBitmap); }
弱援用WeakReference,只有gc发现了弱援用,就会回收掉它的空间。应用场景:ThreadLocalMap, WeakHashMap中的Entry。。
举个例子:ThreadLocalMap中的entry,这个一会咱们重点剖析这里的原理,为什么这么做。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
虚援用PhantomReference,这个援用在gc垃圾回收线程看来,就是没有援用的意思,它的作用是帮忙JVM治理间接内存DirectBuffer。经典的应用场景:NIO。
举个例子:比方DirectBuffer中的Cleaner就是继承了PhantomReference。
public abstract interface DirectBuffer { public abstract long address(); public abstract java.lang.Object attachment(); public abstract sun.misc.Cleaner cleaner();} public class Cleaner extends java.lang.ref.PhantomReference { private static final java.lang.ref.ReferenceQueue<java.lang.Object> dummyQueue; //省略}
下面这四种援用我给大家画一个图,更好了解,如下图所示:
弱援用在ThreadLocal真能避免内存透露吗?
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">弱援用在ThreadLocal真能避免内存透露吗?</span></h3></div>
理解了Java中的四种援用的概念后,咱们来看下ThreadLcoalMap中的Entry继承了WeakReference。到底是为什么?咱们来看如下一个场景。
一个线程thread应用threadLocal对象tl设置了一个value为30M的对象,之后tl=null,不再应用了。tl指向的区域threadLocal对象被gc回收。此时会如下图所示:
这也就解释了,为什么ThreadLocalMap的Entry中的key应用弱援用:
因为若是强援用,即便tl=null,key是强援用的话,仍会指向threadLocal,导致threadLocal不会被回收,造成内存透露。而应用了key弱援用的话,就不会有问题,当tl=null的时候,key是弱援用,gc会间接回收调threadLocal内存中的这个对象。尽管应用了弱援用,然而仍存在内存value指向的强援用,指向了一个堆中的对象,此时key对应的threadLocal曾经回收,key=null,此时,也无法访问到value了。
所以如果一个set的value如果不在应用或threadLoacal不在应用了,肯定要通过remove办法来删除掉之前的key。不然这么使用不当,还是会造成内存透露,导致30M的这个vlaue不会被回收掉
ThreadLocal利用场景
<div class="output_wrapper" id="output_wrapper_id" style="width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: 'Helvetica Neue', Helvetica, 'Hiragino Sans GB', 'Microsoft YaHei', Arial, sans-serif;"><h3 id="hdddd" style="width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;"><span style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">ThreadLocal利用场景</span></h3></div>
最初给大家提几个ThreadLocal的利用场景。你能够想一下,ThreadLocal具备这样个性,能够用在哪里?
Spring 的Transaction机制中的ThreadLocal
最经典的场景就是Spring 的Transaction机制,将一个线程中的事务放入ThreadLocal中,能够在整个办法调用栈中随时取出事务的信息进行批改和操作,不会影响其余的线程的事务。
// TransactionAspectSupport.java private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<TransactionInfo>("Current aspect-driven transaction");
Log4j2等日志框架中的MDC
public class LogbackMDCAdapter implements MDCAdapter { final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>(); }
SpringCloud Sleuth的申请链路跟踪
通过ThreadLocal传递Trace数据,值得一提的是,还通过之前提到过的Thread的另一个本地变量正本inheritableThreadLocal。在创立子线程的时候,会将父线程的inheritableThreadLocals继承下来,这样就实现了TraceContext在父子线程中的传递。代码如下:
public static final class Default extends CurrentTraceContext{ ThreadLocal<TraceContext> DEFAULT = new ThreadLocal<>(); // Inheritable as Brave 3's ThreadLocalServerClientAndLocalSpanState was inheritable static final InheritableThreadLocal<TraceContext> INHERITABLE = new InheritableThreadLocal<>(); final ThreadLocal<TraceContext> local;}
HDFS edits_log的txId自增后放入线程本地正本
HDFS每次创立一个文件,目录等操作会记录一条日志到edits_log中,每条edit_log都有一个txId,会把这个txId记录到以后线程的txId不便在整个线程过程中随时取用,和批改。
/** * FSEditLog 保护元数据(文件目录树)也叫命名空间的批改*/@InterfaceAudience.Private@InterfaceStability.Evolvingpublic class FSEditLog implements LogsPurgeable {// stores the most current transactionId of this thread.private static final ThreadLocal<TransactionId> myTransactionId = new ThreadLocal<TransactionId>() { @Override protected synchronized TransactionId initialValue() { return new TransactionId(Long.MAX_VALUE); }};private long beginTransaaction() { assert Thread.holdsLock(this); txid++; TransactionId id = myTransactionId.get(); id.txid = txid; return now();}
还有很多的场景能够应用。其实通过下面的几个场景,你应该能发现,其实ThreadLocal最罕用的2个场景就是:
1、 线程中,各个办法须要共享变量时应用。除了办法之间传递入参,通过ThreadLocal能够很不便的做到这一点。
2、 多线程操作时,避免并发抵触,保障线程平安。比方个别会拷贝一份数据到线程本地,本人批改本地变量,是线程平安的。
好了,明天的成长记就到这里,你能够在本人的公司遇到的我的项目中或者开源代码中找一下或者注意一下。看看它们是怎么应用ThreadLocal的。
欢送你在评论区,写下你遇见的场景。
本文由博客一文多发平台 OpenWrite 公布!