Java進階 ——— Java多線程(四)之多線程局部變量TreadLocal

在瞭解ThreadLocal之前,一定要確定一個概念:ThreadLocal不是用來解決共享對象的多線程訪問問題的
那麼ThreadLocal在多線程的作用是什麼呢?從下面幾個方面來了解

ThreadLocal的作用

ThreadLocal可以理解爲:線程局部變量, 是每一個線程所單獨持有的。其他線程不能對其進行訪問, 通常是類中的 private static 字段,是對該字段初始值的一個拷貝,它們希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯,

在併發編程的時候,成員變量如果不做任何處理其實是線程不安全的,各個線程都在操作同一個變量,顯然是不行的,並且我們也知道volatile這個關鍵字也是不能保證線程安全的。當然我們可以使用synchorinized 關鍵字來爲此變量加鎖,進行同步處理,從而限制只能有一個線程來使用此變量,但是加鎖會大大影響程序執行效率,此外我們還可以使用ThreadLocal來解決對某一個變量的訪問衝突問題。

當使用ThreadLocal維護變量的時候 爲每一個使用該變量的線程提供一個獨立的變量副本,即每個線程內部都會有一個該變量,這樣同時多個線程訪問該變量並不會彼此相互影響,因此他們使用的都是自己從內存中拷貝過來的變量的副本, 這樣就不存在線程安全問題,也不會影響程序的執行性能。

深入瞭解ThreadLocal使用

先不去分析源碼,僅僅寫個例子,根據例子學習ThreadLocal的簡單使用。

public class ThreadTest {
  //定義一個全局ThreadLocal
	public static ThreadLocal<String> locals= new ThreadLocal<String>();
  
  //開啓三個線程
	public void threadLocalTest(){
		ThreadLocalTest test1 = new ThreadLocalTest("AA");
		ThreadLocalTest test2 = new ThreadLocalTest("BB");
		ThreadLocalTest test3 = new ThreadLocalTest("CC");
		test1.start();
		test2.start();
		test3.start();
	}
}

在線程裏往ThreadLocal塞值,再打印出

public class ThreadLocalTest extends Thread {

	private int a = 5;

	public ThreadLocalTest(String name) {
		super(name);
	}

	@Override
	public void run() {
		super.run();
		try {
			for (int i = 0; i < 5; i++) {
				a += 1;
				ThreadTest.locals.set(a + "");
				Log.e(this.getName(), "value ===== " + ThreadTest.locals.get());
				Thread.sleep(300);
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

查看運行結果

10-24 10:31:46.340 9307-9350/com.t9.news E/AA: value ===== 6
10-24 10:31:46.340 9307-9351/com.t9.news E/BB: value ===== 6
10-24 10:31:46.343 9307-9352/com.t9.news E/CC: value ===== 6
10-24 10:31:46.640 9307-9350/com.t9.news E/AA: value ===== 7
10-24 10:31:46.641 9307-9351/com.t9.news E/BB: value ===== 7
10-24 10:31:46.644 9307-9352/com.t9.news E/CC: value ===== 7
10-24 10:31:46.940 9307-9350/com.t9.news E/AA: value ===== 8
10-24 10:31:46.941 9307-9351/com.t9.news E/BB: value ===== 8
10-24 10:31:46.944 9307-9352/com.t9.news E/CC: value ===== 8
10-24 10:31:47.240 9307-9350/com.t9.news E/AA: value ===== 9
10-24 10:31:47.241 9307-9351/com.t9.news E/BB: value ===== 9
10-24 10:31:47.244 9307-9352/com.t9.news E/CC: value ===== 9
10-24 10:31:47.540 9307-9350/com.t9.news E/AA: value ===== 10
10-24 10:31:47.541 9307-9351/com.t9.news E/BB: value ===== 10
10-24 10:31:47.545 9307-9352/com.t9.news E/CC: value ===== 10

看到每個線程的裏都有自己的String,並且互不影響----,不存在一個線程修改另一個線程中值得情況,對於同一個ThreadLocal對象而言,內部數據僅爲自己獨有,其他線程無法修改

ThreadLocal的理解

瞭解了ThreadLocal的使用,接下來肯定要看看源碼,分析內部實現方式。

就從ThreadLocal 幾個主要方法來學習

public T get()
public void set(T value) 
public void remove()

get()方法是用來獲取ThreadLocal在當前線程中保存的變量副本,set()用來設置當前線程中變量的副本,remove()用來移除當前線程中變量的副本

首先我們來看一下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();
        //然後getMap(t)方法獲取到一個map,類型爲ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //不爲空
        if (map != null) {
        //然後接着下面獲取到<key,value>鍵值對,注意這裏獲取鍵值對傳進去的是  this,而不是當前線程t。
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
            //如果獲取成功,則返回value值
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //否則調用setInitialValue方法返回value
        return setInitialValue();
    }

仔細看看每一步的操作:

  • getMap(t)
/**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
    //返回當前線程中的成員變量threadLocals
        return t.threadLocals;
    }

那麼繼續查看,進入Thread.class ,成員變量threadLocals是什麼

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap是ThreadLocal類的一個內部類,我們繼續取看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<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

ThreadLocalMap的Entry繼承了WeakReference,並且使用ThreadLocal作爲鍵值

如果getMap爲null,則返回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不爲空,就設置鍵值對,
        if (map != null)
            map.set(this, value);
        else
        //爲空,再創建Map
            createMap(t, value);
        return value;
    }
  • 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對象並賦值給Thread中的threadLocals

經過這一系列流程,ThreadLocal是爲每個線程創建變量的副本就很清晰:

  • 首先,在每個線程Thread內部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,這個threadLocals就是用來存儲實際的變量副本的,鍵值爲當前ThreadLocal變量,value爲變量副本(即T類型的變量)。

  • 初始化Thread時,threadLocals爲空,當通過ThreadLocal變量調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,並且以當前ThreadLocal變量爲鍵值,以ThreadLocal要保存的副本變量爲value,存到threadLocals。

  • 然後在當前線程裏面,如果要使用副本變量,就可以通過get方法在threadLocals裏面查找。

總結

1。每個線程中都有一個自己的ThreadLocalMap類對象,可以將線程自己的對象保持到其中,各管各的,線程可以正確的訪問到自己的對象。
2。將一個共用的ThreadLocal靜態實例作爲key,將不同對象的引用保存到不同線程的ThreadLocalMap中,然後在線程執行的各處通過這個靜態ThreadLocal實例的get()方法取得自己線程保存的那個對象,避免了將這個對象作爲參數傳遞的麻煩
3.通過ThreadLocal創建的副本是存儲在每個線程自己的threadLocals中
4.爲何threadLocals的類型ThreadLocalMap的鍵值爲ThreadLocal對象,因爲每個線程中可有多個threadLocal變量
5.在進行get之前,必須先set,否則會報空指針異常;如果想在get之前不需要調用set就能正常訪問的話,必須重寫initialValue()方法。
因爲get方法中,getMap()默認爲null,則返回setInitialValue(),setInitialValue()方法中, T value = initialValue() 默認返回null,最終會報空指針異常


參考

http://www.cnblogs.com/dolphin0520/p/3920407.html

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