ThreadLocal簡單理解

參考資料:http://www.cnblogs.com/dolphin0520/p/3920407.html
先來看看爲什麼用ThreadLocal,上一篇博客說的很好了,就好比,你要讓你的線程鏈接數據庫,如果你讓這些線程共享一個數據庫鏈接的話,就會出問題:
代碼來自參考博客:

class ConnectionManager {

    private static Connection connect = null;

    public static Connection openConnection() {
        if(connect == null){
            connect = DriverManager.getConnection();
        }
        return connect;
    }

    public static void closeConnection() {
        if(connect!=null)
            connect.close();
    }
}

當一個線程正在使用的時候,其他的線程可能會關閉這個鏈接,肯定就出問題了,要解決這個問題,你除了在每個線程單獨的鏈接之外還可以使用ThreadLocal來解決這個問題。

我們把問題簡化:我讓三個線程隨機輸出5到1
先看代碼:

class MyThread extends Thread {

    private int i = 5;

    @Override
    public void run() {
        for(int j = 0; j<20; j++) {
            synchronized (this) {
                if(i>0) {
                  System.out.println(                            
                  Thread.currentThread().getName() + ";" + i--);
                }
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

運行結果:

這裏寫圖片描述

現在,我想讓每個 線程都重5到1輸出一遍,這個問題可以有其他的結局問題,這裏討論的是ThreadLocal,就用ThreadLocal解決
代碼:

class MyThread extends Thread {

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

    public void set() {
        i.set(5);
    }

    public void run() {
        set();
        int si = i.get();
        for(int j = 0; j<20; j++) {
            si = i.get();
            if(si>0) {
                System.out.println(Thread.currentThread().getName() + ";" + si-- );
                i.set(si);
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

結果:
這裏寫圖片描述

這樣就相當於每個線程內部都有一個 i 這個i 只有當前線程自己能訪問,很多地方把ThreadLocal叫做線程本地變量的原因。
現在來看看ThreadLocal怎麼保存變量副本的:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

這是ThreadLocal中經常用到的幾個方法。
先看看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);
        //通過getMap(t)方法得到一個ThreadLocalMap
        if (map != null) {
        //如果map不爲空,這將ThreadLocal作爲鍵值,從map中取出一個Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
            //如果這個Entry不爲空,就將他的值返回
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果當前map爲null,或者entry爲空,調用setInitialValue()方法
        return setInitialValue();
    }

    /*
     * 再來看看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);
        /*取出當前線程的map,如果爲null則創建一個*/
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        /*這個方法最終會返回一個值,不管是創建一個map還是往裏面放一個值*/
        return value;
    }

    /*最後來看看ThreadLocalMap ,這個map到底是什麼*/
    /**
     * 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;
    }


    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
     /*threadLocals定義在,Thread.java中,這是Thread.java中的源碼*/
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /**
     * 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 {

        /**
         * 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;
            }
        }
/*由此可見ThreadLocalMap是ThreadLocal中的一個內部類*/

現在理解ThreadLocal也就簡單一點了:
首先,每個Thread中有一個ThreadLocal的內部類ThreadLocalMap的成員變量threadLocals ,這個threadLocals 就是來保存當前Thread的實際的變量副本的,鍵值爲當前ThreadLocal變量,value爲變量副本(即T類型的變量)。

再來說get(),get()會返回當前ThreadLocal爲鍵的threadLocals中的值,
如果set()之前 get()這時候 會報空指針異常,(原因稍後再說)

然後看看set(T value)的代碼:

    /**
     * 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();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

這時候看就容易了,
然後就是總結了:
1)實際的通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中的;

2)每個線程中可有多個threadLocal變量,記錄不同的副本,類似id,name

3)在進行get之前,必須先set,否則會報空指針異常;

然後就是怎麼處理這個空指針異常:

private T setInitialValue() {
        /*這個可以理解爲得到一個值*/
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        /*取出當前線程的map,如果爲null則創建一個*/
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        /*這個方法最終會返回一個值,不管是創建一個map還是往裏面放一個值*/
        return value;
    }

如果你沒有set()就調用get(),T value = initialValue();這個方法返回的應該是null,這就是原因。
所有如果你不想空指針異常就要重寫這個方法:
例如:

class MyThread extends Thread {

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

    public void set() {
        i.set(5);
    }

    public void run() {
       /*如果將這個 set()去掉,程序會空指針異常*/
       // set();
        int si = i.get();
        for(int j = 0; j<20; j++) {
            si = i.get();
            if(si>0) {
                System.out.println(Thread.currentThread().getName() + ";" + si-- );
                i.set(si);
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

這時如果自己重寫initialValue()方法就可以避免:

class MyThread extends Thread {

    private ThreadLocal<Integer> i = new ThreadLocal<Integer>(){
         protected Integer initialValue() {
                return 5;
            };
    };

    public void set() {
        i.set(5);
    }

    public void run() {
    /*這時就去掉set()不會報錯*/
    //set()
        int si = i.get();
        for(int j = 0; j<20; j++) {
            si = i.get();
            if(si>0) {
                System.out.println(Thread.currentThread().getName() + ";" + si-- );
                i.set(si);
            }
        }
    }

}
public class TestMain {

    public static void main(String ar[]) {

        MyThread m1 = new MyThread(); 

        Thread mt1 = new Thread(m1);
        Thread mt2 = new Thread(m1);
        Thread mt3 = new Thread(m1);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

結果:
這裏寫圖片描述
今晚就寫到這裏了,如果有錯誤,歡迎討論!以後要是深入研究會更新的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章