1. 程式人生 > >Java高併發(六)——ThreadLocal為執行緒保駕護航

Java高併發(六)——ThreadLocal為執行緒保駕護航

       前邊我們講述多執行緒互動,多執行緒引起的安全問題,多執行緒安全的問題解決同步(synchronized、lock、CAS)……這一切的一切起源就是共享資源,共享臨界區的資料安全引起的。那我們從另外一個角度想想呢?每個執行緒有自己的一份資料,是不是就會避免共享資源的資料問題了?ThreadLocal就是從這個角度出發而產生的,好,下邊我們重點看看這個東東。

       一,簡單使用:ThreadLocal是執行緒的區域性變數,也就是說每個執行緒有自己單獨的一塊存獨享資料的空間。屬於執行緒的私有財產。好看下簡單使用:

public class ThreadLocalTest {
    //1,執行緒不安全
    //2,加鎖控制
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //3使用ThreadLocal容器
    public static ThreadLocal<SimpleDateFormat> ts = new ThreadLocal<SimpleDateFormat>();

    public static class ParseDate implements Runnable{
        int i=0;
        public ParseDate(int i ){
            this.i=i;
        }
        @Override
        public void run() {
            try {
                //執行緒不安全
                //Date t = sdf.parse("2018-12-09 12:29:" + i%60);

                //通過threadocal人手一個SimpleDateFormat
                if(ts.get()==null){
                    ts.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                }
                Date t = ts.get().parse("2018-12-09 12:29:" + i%60);
                System.out.println(i + ":" + t);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 1000; i++) {
            es.execute(new ParseDate(i));
        }
    }
}

       二,實現原理(原始碼分析)

       1,set方法:

    /**
     * 一,set方法
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        //1,獲取當前執行緒的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //2,為當前執行緒建立一個ThreadLocalMap
            createMap(t, value);
    }
    
    /**
     * 1,getMap(t)
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * 2,createMap(t, value);
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }


    /**
     * ThreadLocalMap為ThreadLocal的一個內部類
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {}

       2,get方法:這裡注意一下其中的弱應用,方便垃圾回收,看一下弱引用的文章:https://www.cnblogs.com/absfree/p/5555687.htmlhttps://blog.csdn.net/zmx729618/article/details/54093532

    /**
     * 二,get方法
     * 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) {
            //1,Entry為ThreadLocalMap內部類,弱應用
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //2,如果不存在,則進行初始化
        return setInitialValue();
    }


        /**
         * 1,Entry
         * 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;
            }
        }


    /**
     * 2,初始化map
     * 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;
    }

    /**
     * JDK預設的初始化值為null,當然如果我們想修改,則繼承進行method overridden即可
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

       3,何時GC這些執行緒私有物件:

    /**
     * 一,執行緒退出時
     * 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;
    }

       可以看出當exit()時,Thread才會進行各種(包括ThreadLocalMap)清理工作。因此如果我們使用Thread Pool,Thread未必exit(),所以ThreadLocalMap就一直存在,而在使用中如果我們將一下大的物件set進入,就非常容易造成記憶體洩漏的情況。這就需要我們手動進行清除了,ThreadLocal幫我們提供了方法。

    /**
     * 二,呼叫ThreadLocal的remove方法
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

       三,使用效能,其實很明顯的使用自己私有的變數可能比使用公共的變數效能會好很多,這裡引入一個Future的例子,瞭解一下,重點看下,使用ThreadLocal和不適用的效能差距……

public class ThreadLocalHSTest {

    public static final int Get_COUNT = 1000000;
    public static final int THREAD_COUNT = 4;
    static ExecutorService es = Executors.newFixedThreadPool(THREAD_COUNT);

    public static Random rd =new Random(123);

    public static ThreadLocal<Random> trd = new ThreadLocal<Random>(){
        @Override
        protected Random initialValue() {
            return new Random(123);
        }
    };

    public static class RndTask implements Callable<Long>{
        private int mode =0;
        public RndTask(int mode){
            this.mode = mode;
        }

        public Random getRandom(){
            if(mode==0){
                return rd;
            }else if(mode ==1 ){
                return trd.get();
            }else{
                return  null;
            }

        }

        @Override
        public Long call() throws Exception {
            long b = System.currentTimeMillis();
            for (int i = 0; i < Get_COUNT; i++) {
                getRandom().nextInt();
            }

            long e= System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "spend" + (e-b) + "ms");
            return e-b;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Future<Long>[] future = new Future[Get_COUNT];
        for (int i = 0; i < THREAD_COUNT; i++) {
            future[i] = es.submit(new RndTask(0));
        }

        long totalTime = 0;
        for (int i = 0; i < THREAD_COUNT; i++) {
            totalTime +=future[i].get();
        }
        System.out.println("多執行緒訪問同一個Random例項:" + totalTime + "ms");


        for (int i = 0; i < THREAD_COUNT; i++) {
            future[i] = es.submit(new RndTask(1));
        }
        totalTime = 0;
        for (int i = 0; i < THREAD_COUNT; i++) {
            totalTime +=future[i].get();
        }
        System.out.println("使用ThreadLocal包裝Random例項:" + totalTime + "ms");
    }
}


pool-1-thread-2spend311ms
pool-1-thread-4spend330ms
pool-1-thread-1spend331ms
pool-1-thread-3spend331ms
多執行緒訪問同一個Random例項:1303ms
pool-1-thread-3spend28ms
pool-1-thread-2spend31ms
pool-1-thread-1spend30ms
pool-1-thread-4spend31ms
使用ThreadLocal包裝Random例項:120ms

       ThreadLocal,對於執行緒儲存自己的變數,在其生命週期中進行使用是非常方便,但是要注意執行緒池中的使用,以及ThreadLocalMap的垃圾回收,防止出現記憶體溢位……