ThreadLocal- thread safety weapon

Original link: https://www.jylt.cc/#/detail?activityIndex=2&id=9df3fd62d6ee13ff555c30157798b092

What is ThreadLocal

ThreadLocal is used to provide local variables within the thread, which are unique to each thread and cannot be accessed by other threads. It mainly has the following functions:

  • Solving shared variable thread safety issues
  • Global parameter passing in the thread life cycle

One of the most typical applications is the processing of database connection pools. You can refer to this article to read: Application of ThreadLocal in database connection

How to use ThreadLocal

ThreadLocal mainly has the following methods:

public void set(T value); // 存值
public T get(); // 取值
public void remove(); // 移除线程局部变量的引用

ThreadLocal underlying principle

ThreadLocalWhy can thread data be isolated when multi-threaded ? The reason is that each thread Thread maintains a ThreadLocal.ThreadLocalMap reference, which is where the value ThreadLocalMapis stored . ThreadLocalThe reference relationship is as follows:https://oss.jylt.cc/img/content/32ef85ebfc3b3eb8ef83903ee3757cc0/a09b1f73-34ce-4394-b78c-4697d1a1683f.png

The following reference relationships are explained based on the source code.

Thread class

public class Thread implements Runnable {
    
    
    // Thread拥有ThreadLocal.ThreadLocalMap的引用
    ThreadLocal.ThreadLocalMap threadLocals = null
} 

ThreadLocal class structure

Note the red line part of the picture above and Entry extends WeakReference. Why is the key of Entry wrapped into a weak reference?

First of all, we must know the role of weak references. We all know that we usually create objects Object o = new Object(). This method is a strong reference. Before the object ois used up, the object will not be garbage collected because the object is reachable; the object uses After completion, it can be garbage collected because the object is unreachable. If it is a weak reference object and no other strong reference object refers to it, the object will be garbage collected oregardless of any collection execution GC .o

It can be seen that the design of the red line part is to prevent the key from being unable to be GC for a long time, resulting in memory overflow.

public class ThreadLocal<T> {
    
    
	// ThreadLocal的内部类,这个也就是上面Thread里面持有的threadLocals对象
	static class ThreadLocalMap {
    
    
	// Entry是具体存放ThreadLocal数据的容器,可以发现Entry的数据结构跟Hash Map的是比较像的,都是<key,value>形式。此处的Entry的key是ThreadLocal对象,下面会说到
        static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
    
    
                super(k);
                value = v;
            }
        }
  
	// table是Entry数组的原因是,每个线程可能会有多个ThreadLocal对象,这个时候,需要将不同ThreadLocal对象对应的值放到不同下标的Entry数组中,具体如何存放的下面会说到
	private Entry[] table;
    }
}

Method analysis:

set(T value)

This method is to store values ​​in ThreadLocal, as follows:

ThreadLocal<Integer> tl = new ThreadLocal<>();
tl.set(1);

The logic of specific setting values:

public void set(T value) {
    
    
	// 获取当前线程对象
	Thread t = Thread.currentThread();
	// 获取当前线程引用的 threadLocals 对象
	ThreadLocalMap map = getMap(t);
	if (map != null)
		// 设置值
		map.set(this, value);
	else
		// 创建新的引用
		createMap(t, value);
}


ThreadLocalMap getMap(Thread t) {
    
    
	// 获取当前线程持有的threadLocals
	return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    
    
	// 初始化一个 ThreadLocalMap 赋值给当前线程的 threadLocals
	t.threadLocals = new ThreadLocalMap(this, firstValue);
}

private void set(ThreadLocal<?> key, Object value) {
    
    
    Entry[] tab = table;
    int len = tab.length;
  
  	// 计算数据应该存放的位置
    int i = key.threadLocalHashCode & (len-1);

  	// 在进行设置值的时候,为了解决hash冲突,使用了 线性探测法
  	// 如果第 i 个位置已经有值了,则判断下一个位置有没有值,没有值则将数据放入该位置
	// 整个循环的意思是,从上面获取的hash下标开始向后遍历,在遍历过程中如果当前下标的Entry没有值,如果有值,判断Entry的key是不是当前threadLocal对象,如果是,则给当前ThreadLocal设置新的value;如果Entry的key为空,说明该Entry已经没有引用的ThreadLoca了,无法再被访问到,将该无效Entry移除,然后赋值新的key和value
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
    
    
      ThreadLocal<?> k = e.get();

      if (k == key) {
    
    
        e.value = value;
        return;
      }

      if (k == null) {
    
    
        // 说明当前的 threadLocal 对象已经被GC清理,移除失效的 Entry,下面会说到
        replaceStaleEntry(key, value, i);
        return;
      }
    }
    // 说明当前下标的Entry还没有值,初始化一个新Entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
      rehash();
 }

T get()

public T get() {
    
    
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    
    
    // map.getEntry 通过循环遍历的方式查找当前 ThreadLocal
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
    
    
      @SuppressWarnings("unchecked")
      T result = (T)e.value;
      return result;
    }
  }
  return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
    
    
     int i = key.threadLocalHashCode & (table.length - 1);
     Entry e = table[i];
     if (e != null && e.get() == key)
	// Entry的key==当前threadLocal,说明是要查询的Entry
         return e;
      else
	// 通过线性探测法,循环获取下一个下标的Entry,并判断是不是目标Entry
          return getEntryAfterMiss(key, i, e);
}

void remove()

public void remove() {
    
    
  ThreadLocalMap m = getMap(Thread.currentThread());
  if (m != null)
    m.remove(this);
}

private void remove(ThreadLocal<?> key) {
    
    
  Entry[] tab = table;
  int len = tab.length;
  int i = key.threadLocalHashCode & (len-1);
  for (Entry e = tab[i];
       e != null;
       e = tab[i = nextIndex(i, len)]) {
    
    
    if (e.get() == key) {
    
    
      // 移除 key 的引用
      e.clear();
      // 移除 value 的引用
      expungeStaleEntry(i);
      return;
    }
  }
}
  • Precautions

After using ThreadLocal, you must manually call the remove method , otherwise it may cause internal overflow. As mentioned earlier, the key in Entry is a weak reference object, which can avoid memory overflow. But value is a strongly referenced object. If the object of value is still referenced by other objects, value will not be reclaimed by GC. If there are many such values, it will cause memory overflow.

The reason why value may be referenced for a long time is that the life cycle of Thread is much longer than that of objects. During the entire life cycle, many ThradLocals may be created. At this time, there will be a lot of value objects, and there will be no will be garbage collected.

Remove the logic of invalid Entry

private int expungeStaleEntry(int staleSlot) {
    
    
  Entry[] tab = table;
  int len = tab.length;

  // 将当前 Entry 的 value 置为 null
  tab[staleSlot].value = null;
  // 将当前 Entry 置为 null
  tab[staleSlot] = null;
  // Entry 数量 -1
  size--;
  
  Entry e;
  int i;
  // 通过线性探测法将 table 中所有失效的 Entry 都做清理
  for (i = nextIndex(staleSlot, len);
       (e = tab[i]) != null;
       i = nextIndex(i, len)) {
    
    
    ThreadLocal<?> k = e.get();
    if (k == null) {
    
    
      e.value = null;
      tab[i] = null;
      size--;
    } else {
    
    
      int h = k.threadLocalHashCode & (len - 1);
      if (h != i) {
    
    
        tab[i] = null;
        while (tab[h] != null)
          h = nextIndex(h, len);
        tab[h] = e;
      }
    }
  }
  return i;
}

ThreadLocal child thread cannot inherit parent thread data problem

If a child thread is created in a thread, the ThreadLocal data of the child thread and the parent thread cannot be shared, such as the following code:

public static void main(String[] args) {
    
    
  ThreadLocal<Integer> local = new ThreadLocal<>();
  local.set(1);
  System.out.println("父线程get=" + local.get());

  new Thread(() -> {
    
    
    System.out.println("子线程get=" + local.get());
  }).start();
}

// 输出结果:
// 父线程get=1
// 子线程get=null

How to use the parent thread ThreadLocal data in the child thread? You can use InheritableThreadLocalthe following code:

InheritableThreadLocal<Integer> local1 = new InheritableThreadLocal<>();
local1.set(1);
System.out.println("父线程get1=" + local1.get());

new Thread(() -> {
    
    
  System.out.println("子线程get1=" + local1.get());
}).start();

// 打印结果
// 父线程get1=1
// 子线程get1=1

The principle is that when calling the Thread construction method, the local variable of the parent thread will be assigned to the child thread, so that the parent thread data can be used in the child thread.

But this method cannot be used in the thread pool, and the threads in the thread pool are not necessarily created by the current thread.

public class Thread implements Runnable {
    
    
  
  ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  
  public Thread(Runnable target) {
    
    
    init(null, target, "Thread-" + nextThreadNum(), 0);
  }
  
  private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    
    
    init(g, target, name, stackSize, null, true);
  }
  
   private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
    
    
     Thread parent = currentThread();
     //  parent.inheritableThreadLocals 的值是在调用 set 方法时设置的
     if (inheritThreadLocals && parent.inheritableThreadLocals != null)
       		// 将父线程的局部变量赋值给子线程
            this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
   }
}

How to use the ThreadLocal local variable of the parent thread in the thread pool

You can refer to Alibaba’s open source projects: Gitee fast download/transmittable-thread-local

Guess you like

Origin blog.csdn.net/wuchenxiwalter/article/details/131487940