SpringBoot - ThreadLocal を使用して、クラス メンバー変数の同時スレッドの安全性の問題を解決します。

質問

  開発プロセス中に、特定のクラスで可変メンバー変数を使用すると、スレッド セーフティの問題が発生します。これは、クラスが他の依存クラスに対してシングルトン インジェクションされる可能性があり、複数のスレッドが同じ変数を共有して操作することになるためです。の解き方?
  スレッドの安全性の問題に遭遇したとき、私たちが最初に考えるのは を使用することです。遅さを恐れない限り、すべてをロックできます。複数のスレッドの同時アクセス操作を実現するためにロックを使用します。私がロックした場合、操作できるようになる前に私がロックを解除するまで待つ必要があります。しかし、ご存知のとおり、マルチスレッドの同時アクセス時にロックにより一部のスレッドがブロックされて待機することは間違いなく、パフォーマンスに一定の影響を及ぼします。では、ロック以外にそれを回避する方法はあるのでしょうか? 答えは「はい」です。色々な使い方ができますので、お話しましょう〜

スレッドローカルな方法

導入

  1. ThreadLocal文字通りローカル スレッドとして理解され、フルネームは ですThread Local Variable当前线程言い換えれば、これはローカル スレッド変数である現在のスレッド変数であり、他のスレッドに共通の変数が埋め込まれます封闭且隔离的
  2. 変数分離の機能を実現するにはどうすればよいですか? ThreadLocalスレッドごとに独自のコピーを作成でき、各スレッドが独自の内部コピー変数にアクセスして分離効果を達成できるため、共有変数のスレッド安全性の問題が解決されます。
  3. ThreadLocal変数はスレッド内のローカル変数であり、スレッドごとに異なるコピーが存在しますが、コピーは現在のスレッドでのみ使用でき、マルチスレッド共有の問題はありません。
  4. ThreadLocal通常private static、スレッドが終了したときに変更を加えることで、ThreadLocalコピーをリサイクルできます。

ケース

変数のストレージは、 SpringBoot - 統合 AOP 詳細説明 (アスペクト指向プログラミングの側面)の AOP コーディングでも使用されました。ThreadLocalstarttime

ソースコード

セットメソッド


    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
    
    
    	//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程中的变量map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        	//若为空,则初始化当前线程的变量map,key为当前线程,map为变量
            createMap(t, value);
    }
    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
    
    
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
    
    
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

 static class ThreadLocalMap {
    
    

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        //弱引用,若为null时,ThreadLocal被回收,但是map的value还存在,容易造成内存泄漏
        static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
            /** The value associated with this ThreadLocal. */
            Object value;
			//使用Entry保存数据,k为ThreadLocal,v为变量value值
            Entry(ThreadLocal<?> k, Object v) {
    
    
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

	 	...
	 	//其他源码省略
	 	...

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    
    
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
   }
  1. ThreadLocalMapThreadLocalデータを保存するために使用される静的な内部クラスですEntry
  2. Entry弱参照を継承しますWeakReference。キーは現在のスレッドThreadLocal、値は変数値です。

メソッドを取得する


    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
    
    
    	//获取当前线程
        Thread t = Thread.currentThread();
        //获取变量map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
    
    
            //从获取Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
    
    
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

メモリリークの問題

理由

  1. ThreadLocalこれは弱い参照であり、null長すぎるThreadLocalリサイクルされます (これによりEntryメモリ リークを回避できます)。
  2. ThreadLocalMap保存された弱参照はリサイクルされますThreadLocalvalue依然として存在するため、メモリ リークが発生しやすくなります。

引用

  • 强引用: 強参照されたオブジェクトはリサイクルされません。たとえばnew、OOM 例外が発生した場合でも、オブジェクトはリサイクルされません。
  • 软引用: gc 発生時にメモリ不足が判明した場合のみ、ソフト参照のオブジェクトが再利用されます。キャッシュなど、システム メモリが十分な場合、オブジェクトは通常リサイクルされませんが、システム メモリが不足して gc の場合、これらのソフト参照オブジェクトはリサイクルされます。
  • 弱引用: 弱参照オブジェクトは、gc が発生する限りリサイクルされます。
  • 虚引用: ファントム参照は通常、参照キューと組み合わせて使用​​されます。オブジェクトがファントム参照を保持している場合、それは参照がなく、いつでも gc される可能性があることを意味します。主に gc されているオブジェクトのアクティビティを追跡するために使用されます。 d.

解決

の使用が終了したらThreadLocal、最後にremove()メソッドを使用して現在のスレッド変数の値を削除します。

使用するシーン

  1. スレッド間のデータ分離により、各スレッドは ThreadLocal 変数の独自のコピーを作成します。
  2. トランザクション操作を実行し、スレッド トランザクション情報を保存します。
  3. Session セッション管理のためのデータベース接続。
  4. マルチスレッドにおけるデータの同時実行性の不整合の問題を解決します。

ThreadLocal と同期の違い

  1. synchronizedこれはロック機構に基づいており、ある時点では変数やコードに 1 つのスレッドしかアクセスできず、ロックとロック解除の間には境界があり、複数のスレッド間でのデータ共有の競合の問題を解決するために使用されます。
  2. ThreadLocalこれは、各スレッドが独立した変数のコピーを持ち、各スレッドが同時に変数にアクセスでき、並行してアクセスできるが、複数のスレッドによってアクセスされる変数は同じではなく、各スレッドの独立したコピーであるという事実に基づいています。 、これは、複数のスレッドがデータ共有の問題を分離する必要があることを解決するために使用されます。

使い方

コード例

    public static ThreadLocal<String> local = new ThreadLocal<>();
    public static void main(String[] args) {
    
    
    	try {
    
    
        LongStream.range(100000000, 100000005)
                .forEach(a -> new Thread(()-> {
    
    
                    local.set(Thread.currentThread().getName() + "-" + a);
            System.out.println("线程名称:" + Thread.currentThread().getName() + ", local: " + local.get());
        }).start());
        } finally {
    
    
              local.remove();
		}


    }

演算結果

线程名称:Thread-0, local: Thread-0-100000000
线程名称:Thread-3, local: Thread-3-100000003
线程名称:Thread-2, local: Thread-2-100000002
线程名称:Thread-1, local: Thread-1-100000001
线程名称:Thread-4, local: Thread-4-100000004

  実行結果から、ローカルがスレッドから独立していることがわかります。

@Scope マルチケースアノテーションメソッド

導入

  1. アノテーションを使用して@Scope("prototype")、Bean の複数のインスタンスの問題を解決したり、マルチスレッドのクラス メンバー変数の共有の問題を解決したりできます。
  2. Spring の IOC 関数を使用して Bean を管理する場合、デフォルトはシングルトンですが、マルチスレッド環境では、クラスのメンバー変数が変数値の場合、スレッドの安全性の問題が発生します。@Autowired必要に応じて、 useまたはアノテーション注入だけで直接使用できます@Resource

使用

  1. サービス層の利用形式
@Service
@Scope("prototype")
public class XxxService {
    
    

}
  1. 上位層インジェクションを使用する場合は、
    上位層もシングルトンになるかどうかを区別する必要があります。アノテーションインジェクションを使用することをお勧めします@Resource

おすすめ

転載: blog.csdn.net/Andya_net/article/details/129815440