詳細ThreadLocalのクラス

この記事では、知識が含まれています

  • 何ThreadLocalのこと?
  • ThreadLocalの使用シナリオ
  • コアの原則と方法を達成するためにThreadLocal
  • ThreadLocalの使用を注意してください

図書 何1.ThreadLocalこと?

スレッドセーフな広い意味を達成するための方法は次のとおりです。相互排他同期、スリーは、ThreadLocalのは非同期スキームに属し同期シナリオなしで、同期を非ブロック。

ThreadLocalのスレッドローカルストレージが参照される名前は、共有データの各スレッドローカルコピーへの共有データが共有データ可視領域または可変性を制限するために、各スレッドが有する従っているとおり、保存されます。ThreadLocalをロックする必要は欠点層渡されたスレッドセーフといくつかのケースでは、同期高ミューテックスよりも効率的に回避することができるクラスメソッドのパラメータを使用して実装されていません。ThreadLocalの使用に関するクラスRequestContextHolderスプリングフレーム(要求スレッド内の各要求は単離されているので、同じではない情報)などを実装しました。

図書 2.ThreadLocal利用シナリオ

  • 排他的データ、またはそのような一般的に使用されるツールのSimpleDateFormatとして共有データの可視範囲を制限する必要各スレッドのニーズは、スレッドセーフではなく、ThreadLocalのと共に使用することができます
  • 「グローバル」のデータを保存する各スレッドのニーズは、例えば、私たちは、実際のプロジェクトを使用:トークンパラメータの層を減らすThreadLocalの上のすべてのインターフェイスにトークンを参照に、フィルタは、呼び出しを容易にするために、渡されました

各スレッドに排他的データ:第1の使用シナリオをテストするために、次の例のSimpleDateFormat

public class DateFormatUitls {
    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String format(int milSecond){
        Date date = new Date(milSecond * 50000);
        return sdf.format(date);
    }
}

public class ThreadLocalTest {
    static ExecutorService es = Executors.newFixedThreadPool(10);
    
    public static void main(String[] args) {
        try {
            for (int i = 0; i < 1000; i++) {
                int milSecond = i;
                es.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(DateFormatUitls.format(milSecond));
                    }
                });
            }
        }catch (Exception e){
        }finally{
            es.shutdown();
        }
    }
}

同時実行のマルチスレッド共有SimpleDateFomatオブジェクトの存在は、出力は、我々は方法が同期ロックである)(sdf.formatため(解く変換するためにThreadLocalを使用できるように、ある時刻における同じ点、並行性の問題の存在下で見ることができますはい、)効率を考慮してください。

public class DateFormatUitls {

    public static String format(int milSecond){
        Date date = new Date(milSecond * 1000);
        return ThreadLocalSimpleDateFormat.threadLocal.get().format(date);
    }
}

class ThreadLocalSimpleDateFormat extends ThreadLocal{

   static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
}

public class ThreadLocalTest {
    static ExecutorService es = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        try {
            for (int i = 0; i < 1000; i++) {
                int milSecond = i;
                es.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(DateFormatUitls.format(milSecond));
                    }
                });
            }
        }catch (Exception e){
        }finally{
            es.shutdown();
        }
    }
}

そして、第二の試験の一般的なシナリオ:各スレッドのグローバルデータの保存

私は、グローバルなプロジェクトが実際に(流線に多くのコードの後に​​)例えば、トークンを使用して保存するためにここにいます:

/**
 * @author simons.fan
 * @description 过滤器进行鉴权和授权
 **/
@Component
@WebFilter(urlPatterns = "/**")
public class SecutityFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        //TODO
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        String token = servletRequest.getParameter("token");
        //TODO省略参数校验……
        //将token写入ThreadLocal中
        TokenContextHolder.set(StringUtils.isEmpty(token) ? "默认值" : token);
        //TODO省略鉴权……
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        //TODO
    }
}
/**
 * @author simons.fan
 * @description 全局ThreadLocal类型的token容器
 **/
public class TokenContextHolder {

    public static ThreadLocal<String> tokenContext = new ThreadLocal();

    /**
     * 把token放入threadLocal中
     **/
    public static void set(String token) {
        tokenContext.set(token);
    }

    /**
     * 从threadLocal中获取对应线程存储的值
     **/
    public static String get() {
        return tokenContext.get();
    }
}

ThreadLocalのを通して利用可能(トークンを取得するために、任意のインターフェイスのニーズに従うことができ、直接、パラメータの層を通過する必要があり、存在しないトークン・セキュリティ・スレッドなし、にスレッドの各セットに対応したトークンである[ 注:リンク速やかにremove()メソッドを呼び出すためのThreadLocal終了位置 ]

図書 3.ThreadLocal原則とコア方法

ThreadLocalの原則については、我々は最初に知ってもらう必要があり、ThreadLocalMapスレッドとのThreadLocal 3との間の関係を。我々は、各スレッドのThreadオブジェクトを保存することThreadLocalMapオブジェクトがあると言うのセット変数の値をキーとしてKEY_VALUE値ThreadLocal.threadLocalHashCode、ThreadLocalのオブジェクトは、各ThreadLocalのThreadLocalMapアクセスエントリの現在の行ですオブジェクトは、あなたが簡単かつ迅速にローカル変数はThreadLocalの値に格納されているに取得することができ、このキーを使用し、独自のthreadLocalHashCode値が含まれています。そうThreadLocalMapマップ(配列構造)ので、問題のハッシュ衝突であろう(むしろリンクされたリスト構造に比べ、次の空の位置を探索し続けて)ハッシュ衝突が実際に発生したとき、ThreadLocalMapが線形検出方式を使用して[注:このネットムークラスのカリキュラムから結論:同時多目的になる楽しいのJava並行処理ユーティリティ、熟練しJUC、  結果として学習]

スレッド、ThreadLocalの、ThreadLocalMap关系图

ThreadLocalのコアメソッド

  • initialValue() スレッドローカル変数の値を初期化するために使用され、明示的に書き換える必要があり、書き換え言葉は(それがトリガーされるとき、呼び出しのget()メソッド)のロードを遅らせることになる、デフォルト値はnullに戻りません
  • セット(T VAR1):THREADLOCAL手動セット値の明示的なThreadLocalの設定値はinitialValueが呼び出し()メソッドの初期化を行いません。性質及び最終的threadLocalMap.set()メソッドによって操作されるようセットはinitialValue
  • GET():TreadLocalに対応する取得値
  • 削除():ThreadLocalMapからキーと値を削除します
//ThreadLocal的get()方法
public T get() {
	//得到当前线程
	Thread var1 = Thread.currentThread();
	//得到当前线程的ThreadLocalMap对象
	ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
	//如果ThreadLocal没有被显式set()过,则调用initialValue()先初始化
	if (var2 != null) {
		ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
		if (var3 != null) {
			Object var4 = var3.value;
			return var4;
		}
	}
	//调用initialValue()先初始化
	return this.setInitialValue();
}

//ThreadLocal的set()方法
public void set(T var1) {
	Thread var2 = Thread.currentThread();
	ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
	if (var3 != null) {
		//说明不是第一次set,直接覆盖,this表示ThreadLocal
		var3.set(this, var1);
	} else {
		//说明是第一次set,新建map
		this.createMap(var2, var1);
	}
}

//ThreadLocal的remove()方法
public void remove() {
	ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
	if (var1 != null) {
		//删除this对应的threadLocal的value
		var1.remove(this);
	}
}

図書 4.ThreadLocal注意事項

誤っメモリリークや弱い参照を見て、この問題の必要性について話して、メモリリークを引き起こす可能性のThreadLocalの概念を使用しています。

  • メモリリークが:オブジェクトは使用されなくなりましたが、メモリ空間を解放しませんでした
  • 参照の4種類のオブジェクト:

    1.強い参照:ユビキタス手段符号、長い依然として強い参照があるように、ガベージコレクタが参照されるオブジェクトオフ回収はないとして、そのような基準の対象物OBJ =新しいオブジェクト()と同様。

    2.ソフト引用:秒場合は、メモリオーバーフロー例外は、これらのオブジェクトにどうなる前に、オブジェクトに関連付けられたソフト参照のためのいくつかの非必須の説明だけでなく、オブジェクトには、、、第2の回収の回復の範囲に含まれます回復が十分なメモリがないと、メモリがオーバーフロー例外がスローされます。

    3.弱参照:説明非必須オブジェクトは、また、次のガベージコレクションが発生するまで、十分なメモリが弱い回復オフであるかどうかだけに関係なく、次のガベージコレクションを生き残ることができるより柔らかい、オブジェクトへの弱参照に関連付けられているの強度よりも弱い引用さ参照オブジェクトは、関連付けられています。

    4.仮想基準:オブジェクトの生存時間は、ファントム参照オブジェクトインスタンスによって取得することができない影響を与えません。

操作といえば、我々はThreadLocalMapソースに見て、ThreadLocalMap上記本質的にThreadLocalであります:

キーは弱い参照タイプであります

各エントリのためのキーが弱参照型である、値が実行通常の状況ワーカー下強い参照のタイプ、行動ビジネスであり、キーと値を含む通常のGCの外であってもよい;しかし、スレッドが終了していない場合プール内の永続的なスレッドが終了していないなど、キーが弱参照であるので、それは、回復されていない値に対応するキーは、GCされているが、それは、強力な基準値がOOMにつながる、GCされていないからです。参照のGCROOTの単純なチェーンは次のスレッド- > TreadLocalMap - >エントリ (NULL値) - >値、 すなわち参照関係とスレッドとの間の強い値がありました

キーの入力がnullの場合JDKの設計者は、アカウントにこの問題をとっている、ThreadLocalのセット()/削除()メソッドでは、値がGCを支援するために、ヌル値に設定されます。しかし、ワーカースレッドのためには、私たちのビジネスは、それが明示的にこれらのメソッドを呼び出すために行く一般的ではない、処理されたされています。

  • ビジネス・プロセスの最後に正しい姿勢は、対応するキーと値を削除し、明示的な呼び出し()メソッドを削除する必要があります
  • ジェネリックThreadLocalのget()メソッドはinitialValue必須の戻り値()メソッドまたはセット(T VAR1)に対応する方法

最後のステップは、特定のビジネスシナリオを組み合わせたロックの使用、またはのThreadLocalの使用を量るか、そうでなければ、ないにThreadLocalを使用してのThreadLocalを使用することです。


図書 拡張読書:Javaのスレッドプールのコメント

公開された202元の記事 ウォンの賞賛571 ビュー147万+

おすすめ

転載: blog.csdn.net/fanrenxiang/article/details/104261683