JavaにおけるThreadLocalの原理とその使用

7249 ワード

背景
ThreadLocalは、共有変数copyをスレッド空間にコピーするスレッドセキュリティの問題を解決できることを知っています.どうすればいいのでしょうか?
Thread Localって何?
ローカル変数のコピー:
スレッドではなく、スレッドのローカライズされたオブジェクトです.マルチスレッドで動作するオブジェクトがThreadLocalを使用して変数を維持する場合、ThreadLocalは、その変数を使用するスレッドごとに独立した変数のコピーを割り当てます.したがって、各スレッドは、他のスレッドに対応するレプリカに影響を与えることなく、独自のレプリカを独立して変更することができる.スレッドの観点から見ると、この変数はスレッドのローカル変数のようなもので、これもクラス名の「Local」が表す意味です.
Thread Localの背後にある実現メカニズム-原理
1.スレッドごとにThreadLocalMap変数があるので、スレッドごとに独立した変数はThreadLocalMapデータ構造によって維持されていると考えられます.
//Thread.java  
public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal{
    /**
    * 1.       thread.currentThread();
    * 2.       threadLocalMap,     map   。
    */
    public void set(T value) {
        Thread t = Thread.currentThread(); //             ,           。
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // ThreadLocalMap(ThreadLocal,Object), ThreadLocal  key,object  value。
            map.set(this, value);
        else
            createMap(t, value);
    }

     /**
     *
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}

Thread Localの使用方法
ThreadLocalの使用原理を理解し、まずThreadLocalクラスにどのような方法があるかを見てみましょう.InitValue()メソッドset()メソッドget()メソッド
ThreadLocal{
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//this ThreadLocal  
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
}

以下のthreadLocal変数を定義します.private ThreadLocal myThreadLocal1 = new ThreadLocal(); private ThreadLocal myThreadLocal2 = new ThreadLocal(); 実際の使用中にmyThreadLocal 1.get()は、現在のスレッドのthreadLocalMapでmyThreadLocal 1がkeyに対応する値を取得します.myThreadLocal 1.set(「abc」)を呼び出すと、abcを現在のThread.ThreadLocalMap()に自動的に設定します.ThreadLocalタイプset()メソッドを参照してください.
Threadに追加したThreadLocal.ThreadLocalMap変数は、スレッドの終了時に解放する必要があります.Threadクラスexitsメソッドを参照してください.
 /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

threadLocalとthreadの同期ロックの比較
threadLocalは、スレッドごとにローカル変数を維持するので、「空間交換時間」の方法です.thread同期ロックアクセスシリアル化、オブジェクト共有化は、「時間変換空間」の方式です.
threadLocalを正しく使用する方法
threadLocalは複数のスレッド間の干渉を解決するため、各スレッドには独自のthreadLocalMapデータ構造があります.したがって、各スレッドのthreadLocalMapは、同じ参照を指すことはできません.すなわち、各スレッドは、自分のオブジェクトthreadLocalMap.set(this,new Object()を腹で参照して記録する必要があります.このように、スレッド間が干渉しません.
/**
 *    thread    threadLocal.threadlocalMap  ,  
 * @author shawn
 *
 */
public class SafeThreadLocalThread  implements Runnable{
    
    private ThreadLocal myThreadLocal = new ThreadLocal();
    
    public static  int i = 0;
    
    @Override
    public void run() {
        Number number = new Number();//             。
        number.setNo(i++);
        
        //     threadLocal 
        myThreadLocal.set(number);
        
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("number.getNo()" + number.getNo());
    }
    
    public static void main(String[] args) {
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            newCachedThreadPool.execute(new SafeThreadLocalThread());
        }
    }
    
    /**
     *   number                 new 。
     * number.getNo()2
     *number.getNo()3
     *number.getNo()1
     *number.getNo()4
     *number.getNo()0
     */
}

詳細コード:https://github.com/shawnxjf1/J2seCodeExample
異なるレベルのキャッシュ
1.threadLocalはスレッドレベルのキャッシュに相当し、各スレッドはmapコンテナをバインドしてこのコンテナにスレッド内のデータを格納する.2.static変数のキャッシュは、次のようなコードでよく使用されます.
class demo {
 public static Map demoCache= new Hashmap();//  jvm     
}

3.tomcat servlet containerの場合、j 2 ee containerの各コンテナには独自のclassloaderがあります.例えば、tomcatの各アプリケーションには独自のappclassLoaderがあり、自分のclassのみをロードします.例えば、app 1を適用すると、app 2がキャッシュするデータが異なる可能性があります.app 1のdemoCacheを操作するときにapp 2の適用に影響を与えないことを望んでいます.このとき必要なキャッシュ・レベルは、コンテナ・レベル(コンテナごとにclassloaderが異なります)です.最も一般的なコンテナ・レベルのツールは、次のとおりです.
org.apache.commons.beanutils.ContextClassLoaderLocal{
    private final Map valueByClassLoader = new WeakHashMap();
}

apache beanUtilsで登録convertを作成するときは、per(thread)context classloaderレベルのキャッシュに格納されます.もちろん、上記のコードが非コンテナで実行されている場合(classLoaderが1つしかないことを意味する)、その効果はstatic変数キャッシュに相当します.関連コードは参照列[beanutils登録convert]を参照してください.
書き上げて考える
以前はThreadLocalを使うのはすべてネット上からコードの断片を抜粋していましたが、ThreadLocalの原理を理解すると自分でThreadLocalコードを柔軟に使うことができます.原理が分かったら、あなたは頭からThreadLocal情報を表現して実現しますが、copyのネット上のコード説明情報だけでは頭を通っていません.もちろん、すべての技術を深くする必要はありませんが、他のモジュールやコードとドッキングする原理を知っておく必要があります.
リファレンス
参考1:beanUtils登録convert
//ContextClassLoaderLocal    threadLocal
BeanUtilsBean{
      private static final ContextClassLoaderLocal
            BEANS_BY_CLASSLOADER = new ContextClassLoaderLocal() {
                        // Creates the default instance used when the context classloader is unavailable
                        @Override
                        protected BeanUtilsBean initialValue() {
                            return new BeanUtilsBean();
                        }
                    };

    /**
     *     classloader BeanutilsBean
     * Gets the instance which provides the functionality for {@link BeanUtils}.
     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
     * This mechanism provides isolation for web apps deployed in the same container.
     *
     * @return The (pseudo-singleton) BeanUtils bean instance
     */
    public static BeanUtilsBean getInstance() {
        return BEANS_BY_CLASSLOADER.get();
    }

    /**
     * Sets the instance which provides the functionality for {@link BeanUtils}.
     * This is a pseudo-singleton - an single instance is provided per (thread) context classloader.
     * This mechanism provides isolation for web apps deployed in the same container.
     *
     * @param newInstance The (pseudo-singleton) BeanUtils bean instance
     */
    public static void setInstance(final BeanUtilsBean newInstance) {
        BEANS_BY_CLASSLOADER.set(newInstance);
    }
}

ConvertUtilsBean{
   protected static ConvertUtilsBean getInstance() {
        return BeanUtilsBean.getInstance().getConvertUtils();
    }
}