ThreadLocalのは、Javaのインタビューの質問の知識を持っている必要があります

image.png

最初にこの研究ノートを通じてこれらの問題を解決することを望んで、一般的な質問の下にThreadLocalに記載されている古いルーチン、:

  1. ThreadLocalのは、すべての問題を解決するのですか?
  2. どのようにThreadLocalを使用するには?
  3. 何であるかThreadLocalの原則?
  4. ThreadLocalのを使用して、実際のプロジェクトのいくつかの例をできますか?

基本

ThreadLocalのスレッドローカル変数、および異なる共通の変数は以下のとおりです。各スレッドは、この変数のコピーが独立して(設定方法)と、この変数のアクセス(getメソッド)を変更することができます保持していない、とスレッド間の競合。

ThreadLocalの一般的に定義されたクラスのインスタンスであろうprivate static状態を可能にし、ThreadLocalのインスタンスをスレッドであろう、修飾が一緒に結合している、ビジネス、通常いくつかのビジネスThreadLocalのパッケージID(ユーザーIDまたはトランザクションID)と- ID異なるスレッドによって使用されますそれは同じではありません。

使い方

CASE1

ビューの特定のポイントから、ThreadLocalのは、Javaでの並列プログラミングのための追加的なアイデアを提供 - オブジェクト自体はスレッドセーフではありませんが、マルチスレッド同期アクセスの効果を達成する必要がある場合は、同時避け、SimpleDateFormatなどを、あなたはにThreadLocalを使用することができます変数。

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}
复制代码

これが唯一の各スレッドのSimpleDateFormatオブジェクトに対して一度初期化する必要があることに注意してください、実際には、メンバ変数を定義するためのカスタムスレッドのSimpleDateFormatに続いて、オブジェクトが初期化されたときに、新しいスレッドが、効果は同じですが、コードはそれがより規則的に見えるようにします。

ケース2

yunosは、ディスクのデータ移行プロジェクトを冷却する前に、我々は寸法をロックし、ユーザーを追跡する必要がない場合は、各スレッドは、移行プロセスの前に、あなたは現在のユーザーのロックを取得する必要があり、各キーがユーザー情報を使用することです、だから、ThreadLocalの変数を達成するために使用することができます。

image.png

CASE3

次の例では、我々はMyRunnableオブジェクトを定義し、このオブジェクトはMyRunnableスレッド1とスレッド2を使用することであるが、内部ThreadLocal変数により、整数に各スレッドのアクセスは、独自の別のコピーです。

package org.java.learn.concurrent.threadlocal;

/**
 * @author duqi
 * @createTime 2018-12-29 23:25
 **/
public class ThreadLocalExample {
    public static class MyRunnable implements Runnable {

        private ThreadLocal<Integer> threadLocal =
                new ThreadLocal<Integer>();

        @Override
        public void run() {
            threadLocal.set((int) (Math.random() * 100D));

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
            }

            System.out.println(threadLocal.get());
        }
    }


    public static void main(String[] args) throws InterruptedException {
        MyRunnable sharedRunnableInstance = new MyRunnable();

        Thread thread1 = new Thread(sharedRunnableInstance);
        Thread thread2 = new Thread(sharedRunnableInstance);

        thread1.start();
        thread2.start();

        thread1.join(); //wait for thread 1 to terminate
        thread2.join(); //wait for thread 2 to terminate
    }
}
复制代码

ThreadLocalのキー知識ポイント

ソースコード解析

ThreadLocalのスレッドは、それが使用されている方法ですか?以下に示すような原理:スタック上のThreadLocalの参照と参照のスレッドは、マップThreadLocalのオブジェクト(弱い参照パッケージを使用)でのキーであるスレッドオブジェクト、ThreadLocalMap参照を参照し、サービス価値は、変数の値です。

image.png

まず見てjava.lang.Threadコード:

public
class Thread implements Runnable {
    //......其他源码
    /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......其他源码
复制代码

マップにthreadLocalsに可変ポイントをスレッドマップ結合現在のスレッドにThreadLocal変数に格納されThreadLocal.ThreadLocalMap、あるinheritableThreadLocalsもThreadLocal変数に格納されているが、現在のスレッドに格納されている同様の効果が、親スレッドはThreadLocal変数を継承しています。

見てjava.lang.ThreadLocal、クラス、および次のようにインタフェースの主なメンバーは以下のとおりです。

image.png

  1. withInitial方法は、JavaのThreadLocalを初期化するための方法は、8時間後に外部コールのget()メソッドは、サプライヤーによる初期値を決定します。

    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }
    复制代码
  2. 変数の現在のスレッドさらなるコピーが作成されていない場合に呼び出す必要があり、その後、可変現在のスレッドのコピーを得る方法を取得するinitialValue初期値方式を設定するために、メソッドのソースコードを取得し、最初の現在のスレッドによって地図に対応する現在のスレッドを取得し、場合マップが空でない場合、対応する値が取られ、エントリに対応するマップから取り出され、マップが空の場合、初期値はsetInitialValue呼を設定し、マップが空でない場合、現在のエントリThreadLocalのインスタンスに対応する空であることも必要です初期値を設定します。

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    复制代码
  3. 集合メソッド、GETメソッドと同様に、まず、現在のスレッドに対応する地図を取得する地図が空である場合、コールは、地図、地図への変数のそうでない場合、値作成createMap - 現在のThreadLocalのオブジェクトにキーを、値が変数の値です。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    复制代码
  4. 現在のスレッドに結びついたコピーを削除する方法を削除

         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    复制代码
  5. デジタル0x61c88647、この値はHASH_INCREMENTの値は、一般的なハッシュマップは、競合のリストを処理するために使用されるが、ThreadLocalMap競合を検出するための線形方法を使用して、HASH_INCREMENTは参照1、前記選択に応じて、たびにステップサイズを増加させますこの図は、紛争の最小限の可能性を可能にすることです。

    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;
复制代码

サンズデータ共有方法

InheritableThreadLocalは、主に作成された子スレッドを使用するときは、自動的に親スレッドにThreadLocalの変数をサブスレッドのアクセスを実現するために、親スレッドのThreadLocal変数を継承する必要があります。InheritableThreadLocalはThreadLocalをを継承し、childValue、GetMapリクエスト、createMap 3つのメソッドを書き換えます。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * 创建线程的时候,如果需要继承且父线程中Thread-Local变量,则需要将父线程中的ThreadLocal变量一次拷贝过来。
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
    * 由于重写了getMap,所以在操作InheritableThreadLocal变量的时候,将只操作Thread类中的inheritableThreadLocals变量,与threadLocals变量没有关系
    **/
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * 跟getMap类似,set或getInheritableThreadLocal变量的时候,将只操作Thread类中的inheritableThreadLocals变量
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
复制代码

childValueについて、いくつかの単語を言うことが起こったのはどのようにコピー?まず、この方法をThread.init見ます

    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
        //其他源码
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
复制代码

その後ThreadLocal.createInheritedMap見える方法は、最終的に書き換えを行うためにここにchildValue InheritableThreadLocalのnewThreadLocalMapメソッドの呼び出し、我々はここで見ることができます実際にスレッドThreadLocalMapに子供にコピー順番に親スレッドThreadLocalMapに関連したコンテンツです。

       private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
复制代码

ThreadLocalのオブジェクトが回収されるのはいつですか?

キーはThreadLocalのオブジェクト、次いでThreadLocalのオブジェクトが弱い参照に包装されているThreadLocalMap弱参照到達不能であると判定されたThreadLocalのオブジェクトまたはThreadLocalのオブジェクトのマップ後に強い基準点がない場合ように、ガベージコレクションはであろうそれはオフに回収しました。エントリの定義を見てください:

 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のライフサイクルは、長いです:ThreadLocalのオブジェクトは、スレッドは、一般的に、アウト文字列のオブジェクトや他のスレッドをしていないのThreadLocalますです一緒に両方を使用することをお勧めします。

ケーススタディ

ThreadLocalの使用でダボ

私は主にキャッシュを要求されたシーンで使用されているのThreadLocalでダボから例を見つける、特定のコードは次のように:

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)
public class CacheFilter implements Filter {

    private CacheFactory cacheFactory;

    public void setCacheFactory(CacheFactory cacheFactory) {
        this.cacheFactory = cacheFactory;
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) {
            Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation);
            if (cache != null) {
                String key = StringUtils.toArgumentString(invocation.getArguments());
                Object value = cache.get(key);
                if (value != null) {
                    if (value instanceof ValueWrapper) {
                        return new RpcResult(((ValueWrapper)value).get());
                    } else {
                        return new RpcResult(value);
                    }
                }
                Result result = invoker.invoke(invocation);
                if (!result.hasException()) {
                    cache.put(key, new ValueWrapper(result.getValue()));
                }
                return result;
            }
        }
        return invoker.invoke(invocation);
    }
复制代码

この呼び出しはThreadLocalCacheが保存されて使用されます - まず、現在のスレッドがちょうど同じ引数を呼び出すことによって開始されているかどうかを判断するためにリクエストパラメータを使用するRPCコール(呼び出し)リンクで見ることができます。具体的に次のことを達成するために、ThreadLocalCacheを参照してください。

package org.apache.dubbo.cache.support.threadlocal;

import org.apache.dubbo.cache.Cache;
import org.apache.dubbo.common.URL;

import java.util.HashMap;
import java.util.Map;

/**
 * ThreadLocalCache
 */
public class ThreadLocalCache implements Cache {

    //ThreadLocal里存放的是参数到结果的映射
    private final ThreadLocal<Map<Object, Object>> store;

    public ThreadLocalCache(URL url) {
        this.store = new ThreadLocal<Map<Object, Object>>() {
            @Override
            protected Map<Object, Object> initialValue() {
                return new HashMap<Object, Object>();
            }
        };
    }

    @Override
    public void put(Object key, Object value) {
        store.get().put(key, value);
    }

    @Override
    public Object get(Object key) {
        return store.get().get(key);
    }

}
复制代码

RocketMQ

RocketMQで、私もThreadLocalの姿を見つけ、それが送られたシーンのメッセージで使用され、MQClientAPIImplは、特定のキューを選択するために必要なステップを持つサーバーを、達成するためにメッセージを送信するRMQ責任がある、特定のキューを選択別のスレッドは、ここで使用し、独自のインデックス値責任ThreadLocalの機構を備えているとき、あなたはのThreadLocalIndexを達成するために見ることができます:

package org.apache.rocketmq.client.common;

import java.util.Random;

public class ThreadLocalIndex {
    private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>();
    private final Random random = new Random();

    public int getAndIncrement() {
        Integer index = this.threadLocalIndex.get();
        if (null == index) {
            index = Math.abs(random.nextInt());
            if (index < 0)
                index = 0;
            this.threadLocalIndex.set(index);
        }

        index = Math.abs(index + 1);
        if (index < 0)
            index = 0;

        this.threadLocalIndex.set(index);
        return index;
    }

    @Override
    public String toString() {
        return "ThreadLocalIndex{" +
            "threadLocalIndex=" + threadLocalIndex.get() +
            '}';
    }
}
复制代码

概要

この記事ではThreadLocalのに関するいくつかの問題を解決するために主にある:(1)特定のコンセプトは何ですか?(2)どのようなシーンの開発でJavaを使用?原則(3)にThreadLocalは似ているのですか?(4)あなたはを参照することができ、その場合には、オープンソース・プロジェクトを?私はこれらのいくつかの質問は、あなたがそれのいくつかを理解しているかどうかわからないのですか?質問があれば、交換してください。

参考資料

  1. なぜ0x61c88647?
  2. JavaのThreadLocalの
  3. いつ、どのように私はThreadLocal変数を使用する必要がありますか?
  4. 技術的な暗い部屋:ジャワのThreadLocalの理解
  5. メモリリークのThreadLocalで詳細な分析
  6. 「戦闘でのJava並行処理。」
  7. 詳細InheritableThreadLocal
  8. 詳細ThreadLocalの
  9. ThreadLocalの使用シナリオ
  10. データ構造:ハッシュテーブル

バックエンド技術、JVMのトラブルシューティングと最適化、Javaのインタビューの質問、個人の成長と自己管理、および他のトピックの数にこのフォーカス、第一線の開発者は、読者のために働くと成長の経験、あなたはここで何かを得ることを期待することができます提供。

Jwadu

おすすめ

転載: juejin.im/post/5d346ac45188256d6d73623e