ThreadLocal スレッド安全ツール

元のリンク: https://www.jylt.cc/#/detail?activityIndex=2&id=9df3fd62d6ee13ff555c30157798b092

スレッドローカルとは何ですか

ThreadLocal は、スレッド内にローカル変数を提供するために使用されます。ローカル変数は各スレッドに固有であり、他のスレッドからはアクセスできません。主に以下の機能があります。

  • 共有変数のスレッドセーフ問題を解決する
  • スレッドライフサイクル内のグローバルパラメータ転送

最も典型的なアプリケーションの 1 つは、データベース接続プールの処理です。この記事を参照してください:データベース接続における ThreadLocal のアプリケーション

スレッドローカルの使用方法

ThreadLocal には主に次のメソッドがあります。

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

ThreadLocal の基本原理

マルチスレッド時にThreadLocalスレッドデータが分離されるのはなぜですか? その理由は、各スレッド Thread がThreadLocal.ThreadLocalMap 参照を保持し、そこに値がThreadLocalMap格納されるためです。ThreadLocal参照関係は次のとおりです。https://oss.jylt.cc/img/content/32ef85ebfc3b3eb8ef83903ee3757cc0/a09b1f73-34ce-4394-b78c-4697d1a1683f.png

以下、上記の参照関係をソースコードに基づいて説明する。

スレッドクラス

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

ThreadLocal クラス構造

上の図の赤い線に注目してくださいEntry extends WeakReferenceEntry のキーが弱い参照にラップされているのはなぜですか?

まず第一に、弱参照の役割を知っておく必要があります。私たちが通常 オブジェクト を作成することは誰もが知っています。Object o = new Object()このメソッドは強参照です。オブジェクトoが使用される前は、オブジェクトは到達可能であるため、オブジェクトはガベージ コレクションされません。オブジェクトの使用 完了後、オブジェクトに到達できないため、ガベージ コレクションが実行される可能性があります。それが弱参照オブジェクトであり、他の強参照オブジェクトがそれを参照していない場合、そのオブジェクトはoコレクションの実行 GC に関係なくoガベージ コレクションされます。

赤い線の設計は、キーが長時間 GC できなくなり、メモリ オーバーフローが発生するのを防ぐためであることがわかります。

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

メソッド分析:

set(T値)

このメソッドは、次のように値を ThreadLocal に保存します。

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

特定の設定値のロジック:

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

無効な削除()

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;
    }
  }
}
  • 予防

ThreadLocal を使用した後は、必ず削除メソッドを手動で呼び出してください。そうしないと、内部オーバーフローが発生する可能性があります。前述したように、Entry のキーは弱参照オブジェクトであるため、メモリ オーバーフローを回避できます。ただし、value は強参照オブジェクトです。value のオブジェクトが他のオブジェクトから参照されている場合、value は GC によって回収されません。そのような値が多数あると、メモリ オーバーフローが発生します。

value が長時間参照される理由は、Thread のライフサイクルがオブジェクトのライフサイクルよりもはるかに長いためであり、ライフサイクル全体で多数の ThradLocal が作成される可能性があり、このとき、大量の value オブジェクトが存在します。 、ガベージコレクションは行われません。

無効なエントリのロジックを削除します

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 子スレッドは親スレッドからデータを継承できません

スレッド内に子スレッドが作成された場合、次のコードのように、子スレッドと親スレッドの ThreadLocal データを共有することはできません。

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

子スレッドで親スレッドの ThreadLocal データを使用するにはどうすればよいですか? InheritableThreadLocal次のコードを使用できます。

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

原則として、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);
   }
}

スレッド プール内の親スレッドの ThreadLocal ローカル変数を使用する方法

Ali のオープンソース プロジェクトを参照できます: Gitee Speed download/transmittable-thread-local

おすすめ

転載: blog.csdn.net/wuchenxiwalter/article/details/131487940
おすすめ