ThreadPoolExecutor 與 ThreadLocal 配合使用中出現(xiàn)數(shù)據(jù)不一致

ThreadPoolExecutor 與 ThreadLocal 配合使用中出現(xiàn)數(shù)據(jù)不一致問題

前段時間寫過一段測試代碼,發(fā)現(xiàn)使用了ThreadLocal出現(xiàn)了數(shù)據(jù)不一致的問題,之前也一直用過,沒有出現(xiàn)過.所以感到很疑惑.于是針對這個case研究了下源碼

  • 單元測試代碼
/**
 * <p>
 * 測試ThreadLocal結(jié)合ThreadPoolExecutor是否存在數(shù)據(jù)不安全情況
 * </p>
 * 
 * @author sunla
 * @version 1.0
 */
public class ThreadLocalTest {

    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(2, 2, 1000, TimeUnit.MILLISECONDS,
            new LinkedBlockingDeque<Runnable>(20), new ThreadFactoryBuilder().setNameFormat("name-%s").build(),
            new ThreadPoolExecutor.AbortPolicy());
    private static final ThreadLocal<String> LOCAL = new ThreadLocal<String>();

    @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()));
    }
}
  • 輸出結(jié)果
NULL
main start

很奇怪.ThreadLocal 就是為了保持回話中變量共享 為什么不一致呢
我們平時使用ThreadLocal的場景 可以理解為會話中的數(shù)據(jù)共享,那怎么在這里出現(xiàn)了與期望不一致的結(jié)果呢?其實這跟ThreadLocal的內(nèi)部實現(xiàn)有關系.

ThreadLocal 內(nèi)部實現(xiàn)

/**
     * 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是內(nèi)部實現(xiàn)的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實現(xiàn)
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<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        //TO DO
  }

看了threadLocal的實現(xiàn) 我們知道了,原來thread local 實例有個map 存儲的key就是線程的引用,value就是需要共享的變量.
那我們上面的代碼 難道獲取的不是同一個線程?
看過ThreadPoolExecutor實現(xiàn)的就知道 真的不是一個
ThreadPoolExecutor 實現(xiàn)

/**
* 添加工作線程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
        //TO DO
        w = new Worker(firstTask);
        final Thread t = w.thread;
        //TO DO
    }
/** 工作類 實現(xiàn)runnable接口 */
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        Worker(Runnable firstTask) {
            setState(-1);
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
        /** 重寫run方法 最終執(zhí)行的就是runWorker */
        public void run() {
            runWorker(this);
        }
    }

這下真相大白了,原因在線程池中會開辟新的線程執(zhí)行task.如果在主線程中(main) 放入到ThreadLocal的value在task中獲取到的就不再是main線程的ref了.而且線程池自己開辟的. 所以導致數(shù)據(jù)不一致.

這里提個問題 threadlocal有個remove方法,
如果我們不顯示調(diào)用 會怎么樣?

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,048評論 25 709
  • layout: posttitle: 《Java并發(fā)編程的藝術》筆記categories: Javaexcerpt...
    xiaogmail閱讀 6,018評論 1 19
  • 感恩錢寶寶的魔力,開通了流量,及時掌握家長群里的動態(tài),感恩學校網(wǎng)絡故障,心里不再惦念手機里未刷的圈,未更的博。...
    娜些事閱讀 442評論 0 0
  • 戀上一個地方, 是因為那里一花一草一木的呼吸, 隨同自己的心跳。 時光慵懶, 模糊了光和影。 緩沖, 水珠滑落指尖...
    行云如風閱讀 165評論 1 1
  • 每個人都試過與他人作「第一次約會」,希望在第一次認識這位新朋友時,展現(xiàn)最真和最好的一面,既興奮又緊張。以下有些要點...
    怡心約會閱讀 225評論 0 0

友情鏈接更多精彩內(nèi)容