ThreadPoolExecutorとThreadLocalの併用によるデータの不一致

4747 ワード

ThreadPoolExecutorとThreadLocalの併用でデータの不一致が発生


この間テストコードを書いたが、ThreadLocalを使ってデータが一致しないという問題があった.だから疑問に思う.そこでこのcaseについて次のソースコードを研究した.
  • ユニットテストコード
  • /**
     * 

    * ThreadLocal ThreadPoolExecutor *

    * * @author sunla * @version 1.0 */ public class ThreadLocalTest { private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(2, 2, 1000, TimeUnit.MILLISECONDS, new LinkedBlockingDeque(20), new ThreadFactoryBuilder().setNameFormat("name-%s").build(), new ThreadPoolExecutor.AbortPolicy()); private static final ThreadLocal LOCAL = new ThreadLocal(); @Test public void startTest() throws Exception { LOCAL.set("main start"); EXECUTOR.execute(()->{ System.out.println( String.format("value is %s", LOCAL.get())); try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println( String.format("value is %s", LOCAL.get())); } }
  • 出力結果
  • NULL
    main start
    

    おかしいですね.ThreadLocalとは、返信中の変数共有を維持するためになぜ一致しないのか.私たちが普段ThreadLocalを使用しているシーンは、セッション中のデータ共有と理解できるが、なぜここで期待と一致しない結果になったのだろうか.実はこれはThreadLocalの内部実装と関係がある.
    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();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
            /** threadLocalMap map
             *   key ThreadLocal 
             *   ?    
             */
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
        /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
        /**  thread threadlocalmap */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
  • ThreadLocalMap
  • を実現
    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.
             */
            static class Entry extends WeakReference> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal> k, Object v) {
                    super(k);
                    value = v;
                }
            }
            //TO DO
      }
    

    threadLocalの実装を見て、thread localのインスタンスにmapが格納されているkeyがスレッドの参照であり、valueは共有する必要がある変数であることが分かった.私たちの上のコードは同じスレッドではありませんか?ThreadPoolExecutorの実装を見たことがあるが、本当にThreadPoolExecutorの実装ではないことを知っている.
    /**
    *  
    */
    private boolean addWorker(Runnable firstTask, boolean core) {
            //TO DO
            w = new Worker(firstTask);
            final Thread t = w.thread;
            //TO DO
        }
    /**    runnable  */
    private final class Worker
            extends AbstractQueuedSynchronizer
            implements Runnable
        {
            Worker(Runnable firstTask) {
                setState(-1);
                this.firstTask = firstTask;
                this.thread = getThreadFactory().newThread(this);
            }
            /**  run   runWorker */
            public void run() {
                runWorker(this);
            }
        }
    

    これは真相が明らかになった.なぜなら、オンラインプールには新しいスレッドが開いてtaskを実行するからだ.メインスレッドにThreadLocalに入れるvalueがtaskで取得するのはmainスレッドのrefではない.スレッドプール自体が開いていますデータの不一致を招く.
    ここで質問ですthreadlocalにはremoveメソッドがあります.呼び出しを表示しないとどうなりますか?