关于java:JDK成长记12ThreadLocal-下

41次阅读

共计 8121 个字符,预计需要花费 21 分钟才能阅读完成。

上一节你弄懂了 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.Evolving
public 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 公布!

正文完
 0