インタビューは言っていない、ThreadLocalのを聞いて、あなたはしません

ThreadLocalのは何ですか

前のインタビュー尋ねたのThreadLocalは常に無知に見えることを強制するとき、ちょうどこの男を知って、彼がやってに使用されているものを理解していないが、彼の原則を知りません。彼は、表面とマルチスレッド、クラスに関連するスレッド同期ツールでは、実際に彼は、スレッド同期メカニズムとは何の関係もありません。スレッド同期メカニズムは、複数のスレッドが同じ変数を共有しており、ThreadLocalの各スレッドは、コピーに対応する他のスレッドに影響を与えることなく、変数の独自のコピーを変更することができ、スレッドごとに変数の個別のコピーを作成することです
公式のAPIが導入されています。このクラスはスレッドローカル(スレッドローカル)変数を提供します。これらの変数は、各スレッドは、変数の初期化コピーとは独立した独自のローカル変数を、持っている(これを介して取得や方法を設定します)ので、アクセス変数に、その正常な対応とは異なります。ThreadLocalのインスタンスプライベート静的フィールドは、典型的にはクラスであり、それらは一つのスレッドを述べることを望む(例えば、ユーザIDまたはトランザクションID)が対応付けられています。

ThreadLocalの的API

ThreadLocalのは、4つのメソッドを定義しています。

  • ()を取得:このスレッドローカルの現在のコピーの値を返します。
  • セット(T値):指定された値に現在のコピースレッドローカル変数の値
  • initialValue():現在の初期値のこのスレッドローカルコピーを返します。
  • 削除():このスレッドローカルの現在のコピーの値を削除します。
  • 、このクラスが実装の主要なスレッドの分離メカニズムであるThreadLocalMap特に重要ThreadLocalの静的内部クラスもあります。()()を設定し、取得する ()を除去し、内部動作クラスに基づいて、各変数のスレッドのための格納されたキー値のコピーとThreadLocalMap、変数の対応するスレッドのコピーの現在の値に対するキーThreadLocalのオブジェクト。
    ThreadLocalのは交換するスペースですので、どのスレッドの安全性の問題がないことを当然のことながら、お互いに影響を与え、自分のThreadLocalMap、そのThreadLocalMap操作を持っている、つまり、各スレッドは、独自にThreadLocalオブジェクトを持っていることを想像し、ソリューションのセキュリティ。

ユースケース

お互いに影響を与え、各スレッドはカウント値が何回も行って何かをやって自分を記録し必要であると仮定すると、我々は、各スレッドの彼らのカウント値を変更する必要が実行されているといない、それが電流を節約するために、ここで良い選択にThreadLocal、ThreadLocalのですスレッドのローカル変数のコピーは、このカウントです。

public class SeqCount {

    private static ThreadLocal<Integer> seqCount = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };


    public int nextSeq() {
        seqCount.set(seqCount.get() +1);
        return seqCount.get();
    }

    public static void main(String [] args) {
        SeqCount seqCount = new SeqCount();

        SeqThread seqThread1 = new SeqThread(seqCount);
        SeqThread seqThread2 = new SeqThread(seqCount);
        SeqThread seqThread3 = new SeqThread(seqCount);
        SeqThread seqThread4 = new SeqThread(seqCount);

        seqThread1.start();
        seqThread2.start();
        seqThread3.start();
        seqThread4.start();
    }

    public static class SeqThread extends Thread {

        private SeqCount seqCount;

        public SeqThread(SeqCount seqCount) {
            this.seqCount = seqCount;
        }

        @Override
        public void run() {
            for (int i=0; i<3; i++) {
                System.out.println(Thread.currentThread().getName()+" seqCount:"+seqCount.nextSeq());
            }
        }
    }
 }
复制代码

結果:

SimpleDateFormatのスレッドの安全性を解決

私たちは、その後、各スレッドのローカル変数のコピーとしてのSimpleDateFormatは、各スレッドは、独自のSimpleDateFormatを持っているということです、何のスレッドの安全性の問題が存在しない、マルチスレッドスレッド安全性の問題でのSimpleDateFormatが存在している知っています。

public class SimpleDateFormatDemo {

    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";

    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<>();

    /**
     * 获取线程的变量副本,如果不覆盖initialValue方法,第一次get将返回null,故需要创建一个DateFormat,放入threadLocal中
     * @return
     */
    public DateFormat getDateFormat() {
        DateFormat df = threadLocal.get();
        if (df == null) {
            df = new SimpleDateFormat(DATE_FORMAT);
            threadLocal.set(df);
        }
        return df;
    }

    public static void main(String [] args) {
        SimpleDateFormatDemo formatDemo = new SimpleDateFormatDemo();

        MyRunnable myRunnable1 = new MyRunnable(formatDemo);
        MyRunnable myRunnable2 = new MyRunnable(formatDemo);
        MyRunnable myRunnable3 = new MyRunnable(formatDemo);

        Thread thread1= new Thread(myRunnable1);
        Thread thread2= new Thread(myRunnable2);
        Thread thread3= new Thread(myRunnable3);
        thread1.start();
        thread2.start();
        thread3.start();
    }


    public static class MyRunnable implements Runnable {

        private SimpleDateFormatDemo dateFormatDemo;

        public MyRunnable(SimpleDateFormatDemo dateFormatDemo) {
            this.dateFormatDemo = dateFormatDemo;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" 当前时间:"+dateFormatDemo.getDateFormat().format(new Date()));
        }
    }
}
复制代码

結果:

ソースコード解析

ThreadLocalMap

内部侵入ThreadLocalMapはキーと値のストアを使用して行います。

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

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

上記キーソースコードのThreadLocalで、値が値、エントリー継承弱い参照であるため、基準(ThreadLocalのインスタンス)に対応するエントリキーが弱参照です。

セット(ThreadLocalのキー、オブジェクト値)

 /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            //根据ThreadLocal的散列值,查找对应元素在数组中的位置
            int i = key.threadLocalHashCode & (len-1);
            //采用线性探测法寻找合适位置
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //key存在,直接覆盖
                if (k == key) {
                    e.value = value;
                    return;
                }
                // key == null,但是存在值(因为此处的e != null),说明之前的ThreadLocal对象已经被回收了
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //ThreadLocal对应的key实例不存在,new一个
            tab[i] = new Entry(key, value);
            int sz = ++size;
            //清楚陈旧的Entry(key == null的)
            // 如果没有清理陈旧的 Entry 并且数组中的元素大于了阈值,则进行 rehash
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
复制代码

集合演算と地図が異なるセットを設定し、ハッシュ衝突を解決する方法はチェーンマップアドレス方式を使用して、ここでは、オープン・アドレス可能(リニアプローブ)です。セット()メソッドreplaceStaleEntry()とcleanSomeSlots()を、これらの2つの方法は、キー== NULLアウト例は、メモリリークをクリアすることができます。

getEntry()

private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }
复制代码

オープン・アドレス指定、keu現在ハッシュ値インデックスの結果としてアレイ要素へと最初の番号(キーハッシュ値)を推測しながら、一対一ではない対応するキーである場合、我々が見ています要素は、その後、直接返却、またはgetEntryAfterMissを呼び出します

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
复制代码

ここでは検知で次の要素を探してきた、私たちが探しているものを見つけるためにある重要な要素を知っています。ここで、キー==ヌル、メモリリークを防ぐために、GCの回復の賛成でexpungeStaleEntryを呼び出します。

なぜThreadLocalのメモリリーク

キーが== nullの場合、GCはこの領域を再利用しますが、彼と現在のため、値は、回復できないかもしれないときにThreadLocalインスタンスへのキーThreadLocalMap、彼は弱参照で、我々は、GCの回復の賛成でその弱い参照を知っていますスレッド間の関係への強い参照もあります。このため、強い参照のために、それは価値につながる回復することができないスレッドオブジェクトは、この関係の強参照を排除していない場合は、それがOOMを表示されることがあります。時には私たちは、remove()メソッドの明示的な治療をThreadLocalMap呼び出します。

概要

  • ThreadLocalのは、共有変数の問題を解決することではない、またそれは、スレッドの同期を調整することで、彼は自分の状態と参照メカニズムを管理するために、各スレッドを容易にすることです。
  • それぞれが、彼は鍵をThreadLocalの内部ThreadLocalMap保存されているのThreadLocalのインスタンスであり、彼の価値は、現在のスレッドのローカル変数のコピーの値です。

おすすめ

転載: juejin.im/post/5d427f306fb9a06b122f1b94