JDK growth 12: ThreadLocal

Posted by wmolina on Thu, 21 Oct 2021 01:55:35 +0200

In the last section, you understood what ThreadLocal is, its basic usage, and the underlying principle of the get method. In this section, let's continue to study in depth:

  • ThreadLocal set source code principle
  • Strong reference, weak reference, soft reference and virtual reference in JVM
  • Application of weak reference in ThreadLocal
  • Analysis of ThreadLocal memory leak
  • ThreadLocal application scenario example

ThreadLocal set method source code principle

ThreadLocal set method source code principle

If you have the experience of reading the get method of threadLocal, the source code of the set method will become very simple. The source code of set is as follows:

 public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
      map.set(this, value);
    else
      createMap(t, value);
  }

It is as like as two peas. I believe that I don't need to draw pictures, and you can understand that the setInitialValue of get is almost the same as that of the last method, but there is no initialValue() method.

If the current thread uses threadLcoal.set(Obejct) for the first time (assuming that the current thread has not called the get method before), a threadLocalMap with a default size of 16 will be created, and the key will be set as threadLocal Object and the value will be set as a corresponding Object.

If it's the second time set is willing to go to map.set(this, value); The branch of this sentence directly sets a key value pair in the threadLocalMap of the current thread.

As shown in the figure below:

Basic knowledge of strong reference, weak reference, soft reference and virtual reference in JVM

Basic knowledge of strong reference, weak reference, soft reference and virtual reference in JVM

Do you remember ThreadLocalMap, a local variable that each Thread has? The core data structure in this Map is an Entry, which represents the data of the Key value pair. The Key value is the ThreadLocal object and the value is the stored object data. The code is as follows:

  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;
      }
    }
  }

This Entry inherits an object of WeakReference. If you are familiar with JVM, you may know what this object is. It is called weak reference.

In java, there are four kinds of object references: strong reference, soft reference, weak reference and virtual reference, which is one of the important criteria for jvm memory recycling. Let me briefly introduce what they are and what scenarios they are generally used in.

Strong reference StrongReference, an object declared generally, is a strong reference. Usage scenarios, such as Loan l = new Loan(); l is a strong reference. If gc finds that an object is strongly referenced and pointed to, even OOM will not recycle it if the JVM space is insufficient.

Soft reference. When the JVM space is insufficient, gc will reclaim the soft reference space first. Usage scenario: suitable for caching.

For example, Android uses Map to cache bitmap data.

private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();


public void addBitmapToCache(String path) {
    // Strongly referenced Bitmap object
    Bitmap bitmap = BitmapFactory.decodeFile(path);

    // Soft referenced Bitmap object
    SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);

    // Add the object to the Map to cache it
    imageCache.put(path, softBitmap);
  }

Weak reference WeakReference. Whenever gc finds a weak reference, it will reclaim its space. Usage scenario: threadlocalmap, Entry in weakhashmap..

For example: the entry in ThreadLocalMap. We will focus on the principle here and why.

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
      super(k);
      value = v;
    }
  } 

The virtual reference PhantomReference means no reference in the view of the gc garbage collection thread. Its function is to help the JVM manage the direct memory DirectBuffer. Classic usage scenario: NIO.

For example, the Cleaner in DirectBuffer inherits phantom reference.

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;

   //ellipsis

}

Let me draw a diagram for you to better understand the above four quotations, as shown in the figure below:

Can weak references in ThreadLocal really prevent memory leakage?

Can weak references in ThreadLocal really prevent memory leakage?

After understanding the concepts of four kinds of references in Java, let's take a look at the Entry in ThreadLcoalMap, which inherits WeakReference. Why on earth? Let's look at the following scenario.

A thread uses the threadLocal object tl to set an object with a value of 30M. Then tl=null and is no longer used. The threadLocal object of the region pointed to by tl is recycled by gc. As shown in the following figure:

This explains why the key in the Entry of ThreadLocalMap uses a weak reference:

Because if it is a strong reference, even if tl=null and key is a strong reference, it will still point to threadLocal, so threadLocal will not be recycled, resulting in memory leakage. If the key weak reference is used, there will be no problem. When tl=null, the key is a weak reference, and gc will directly recycle the object in threadLocal memory. Although a weak reference is used, there is still a strong reference to memory value, pointing to an object in the heap. At this time, the threadLocal corresponding to key has been recycled, and key=null. At this time, value cannot be accessed.

Therefore, if the value of a set is not used or threadload is not used, the previous key must be deleted through the remove method. Otherwise, improper use will still cause memory leakage, so that the 30M vlaue will not be recycled

ThreadLocal application scenario

ThreadLocal application scenario

Finally, I'll give you some application scenarios of ThreadLocal. You can think about where ThreadLocal can be used with this feature?

ThreadLocal in Spring's Transaction mechanism

The most classic scenario is Spring's Transaction mechanism, which puts the Transaction in one thread into ThreadLocal. The Transaction information can be taken out from the whole method call stack at any time for modification and operation without affecting the transactions of other threads.

 // TransactionAspectSupport.java
 private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
 new NamedThreadLocal<TransactionInfo>("Current aspect-driven transaction");

MDC in log frameworks such as Log4j2

  public class LogbackMDCAdapter implements MDCAdapter {
    final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, String>>();
  }

Request link tracking for SpringCloud Sleuth

Trace data is passed through ThreadLocal. It is worth mentioning that it is also passed through another local variable copy of the Thread mentioned earlier, inheritableThreadLocal. When creating a child Thread, the inheritableThreadLocals of the parent Thread will be inherited, so as to realize the transmission of TraceContext in the parent and child threads. The code is as follows:

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_ The txId of log is automatically incremented and put into the local copy of the thread

Each time HDFS creates a file, directory, etc., a log will be recorded to edits_log, each edit_log has a txId, which will be recorded into the txId of the current thread for easy access and modification at any time during the whole thread process.

/**
  * FSEditLog Maintaining metadata (file directory tree) is also called namespace modification
 */
@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();
 }

There are many more scenarios to use. In fact, through the above scenarios, you should find that the two most commonly used scenarios for ThreadLocal are:

1. In the thread, each method needs to share variables. In addition to passing in parameters between methods, this can be easily done through ThreadLocal.

2. During multi-threaded operation, prevent concurrent conflict and ensure thread safety. For example, it is thread safe to copy a copy of data to the local thread and modify local variables by yourself.

Well, that's all for today's growth. You can find or pay attention to the projects encountered by your company or open source code. See how they use ThreadLocal.

You are welcome to write down the scenes you met in the comment area.

This article is composed of blog one article multi posting platform OpenWrite release!

Topics: Java Big Data Back-end