ThreadLocal實現原理

一、概述
ThreadLocal官網解釋:

  This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable.  {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID)

->翻譯過來的大概意思就是:ThreadLocal類用來提供線程內部的局部變量。這些變量在多線程環境下訪問(通過get或set方法訪問)時能保證各個線程裏的變量相對獨立於其他線程內的變量,ThreadLocal實例通常來說都是private static類型。
總結:ThreadLocal不是爲了解決多線程訪問共享變量,而是爲每個線程創建一個單獨的變量副本,提供了保持對象的方法和避免參數傳遞的複雜性。

ThreadLocal的主要應用場景爲按線程多實例(每個線程對應一個實例)的對象的訪問,並且這個對象很多地方都要用到。例如:同一個網站登錄用戶,每個用戶服務器會爲其開一個線程,每個線程中創建一個ThreadLocal,裏面存用戶基本信息等,在很多頁面跳轉時,會顯示用戶信息或者得到用戶的一些信息等頻繁操作,這樣多線程之間並沒有聯繫而且當前線程也可以及時獲取想要的數據。


二、實現原理

ThreadLocal可以看做是一個容器,容器裏面存放着屬於當前線程的變量。ThreadLocal類提供了四個對外開放的接口方法,這也是用戶操作ThreadLocal類的基本方法:
(1) void set(Object value)設置當前線程的線程局部變量的值。
(2) public Object get()該方法返回當前線程所對應的線程局部變量。
(3) public void remove()將當前線程局部變量的值刪除,目的是爲了減少內存的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。
(4) protected Object initialValue()返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,並且僅執行1次,ThreadLocal中的缺省實現直接返回一個null。

可以通過上述的幾個方法實現ThreadLocal中變量的訪問,數據設置,初始化以及刪除局部變量,那ThreadLocal內部是如何爲每一個線程維護變量副本的呢?

其實在ThreadLocal類中有一個靜態內部類ThreadLocalMap(其類似於Map),用鍵值對的形式存儲每一個線程的變量副本,ThreadLocalMap中元素的key爲當前ThreadLocal對象,而value對應線程的變量副本,每個線程可能存在多個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.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;
    }

    /**
     * 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);
    }

    /**
     * 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);
     }

    /**
     * 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類中的幾個主要的方法,他們的核心都是對其內部類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;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        //....其他的方法和操作都和map的類似
}

總之,爲不同線程創建不同的ThreadLocalMap,用線程本身爲區分點,每個線程之間其實沒有任何的聯繫,說是說存放了變量的副本,其實可以理解爲爲每個線程單獨new了一個對象。

 

二、範例解析

假設三個人想從鏡子中看自己,第一個方案就是每人發一個鏡子互不干擾,第二個方案就是隻有一個鏡子,一個人站在鏡子前其他人要排隊等候,第三個方案就是我這裏發明了一種“魔鏡”,所有人站在鏡子前可以並且只能看到自己!!!

        public static void main(String[] args) {
		//Mirror是個單例的,只構建了一個對象
		Mirror mirror = new Mirror("魔鏡");
		
		//三個線程都在用這面鏡子
		MirrorThread thread1 = new MirrorThread(mirror,"張三");
		MirrorThread thread2 = new MirrorThread(mirror,"李四");
		MirrorThread thread3 = new MirrorThread(mirror,"王二");
		
		thread1.start();
		thread2.start();
		thread3.start();
	}

很好理解,創建了一面鏡子,3個人一起照鏡子。

MirrorThread:

public class MirrorThread extends Thread{
	private Mirror mirror;
	
	private String threadName;
	
	public MirrorThread(Mirror mirror, String threadName){
		this.mirror = mirror;
		this.threadName = threadName;
	}
		
	//照鏡子
	public String lookMirror() {
		return threadName+" looks like "+ mirror.getNowLookLike().get();
	}
	
	//化妝
	public void makeup(String makeupString) {
		mirror.getNowLookLike().set(makeupString);
	}
	
	@Override
        public void run() {
		int i = 1;//閾值
		while(i<5) {
			try {
				long nowFace = (long)(Math.random()*5000);
				sleep(nowFace);
				StringBuffer sb = new StringBuffer();
				sb.append("第"+i+"輪從");
				sb.append(lookMirror());
				makeup(String.valueOf(nowFace));
				sb.append("變爲");
				sb.append(lookMirror());
				System.out.println(sb);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			i++;
		}
	}
}

這個類也很好理解,就是不斷的更新自己的外貌同時從鏡子裏讀取自己的外貌。

重點是Mirror:

public class Mirror {
	private String mirrorName;
	
	//每個人要看到自己的樣子,所以這裏要用ThreadLocal
	private ThreadLocal<String> nowLookLike;
	
	public Mirror(String mirrorName){
		this.mirrorName=mirrorName;
		nowLookLike = new ThreadLocal<String>();
	}
 
	public String getMirrorName() {
		return mirrorName;
	}
 
	public ThreadLocal<String> getNowLookLike() {
		return nowLookLike;
	}
}

對每個人長的樣子用ThreadLocal類型來表示。

先看測試結果:

第1輪從張三 looks like null變爲張三 looks like 3008
第2輪從張三 looks like 3008變爲張三 looks like 490
第1輪從王二 looks like null變爲王二 looks like 3982
第1輪從李四 looks like null變爲李四 looks like 4390
第2輪從王二 looks like 3982變爲王二 looks like 1415
第2輪從李四 looks like 4390變爲李四 looks like 1255
第3輪從王二 looks like 1415變爲王二 looks like 758
第3輪從張三 looks like 490變爲張三 looks like 2746
第3輪從李四 looks like 1255變爲李四 looks like 845
第4輪從李四 looks like 845變爲李四 looks like 1123
第4輪從張三 looks like 2746變爲張三 looks like 2126
第4輪從王二 looks like 758變爲王二 looks like 4516

ThreadLocal應用其實很多,例如Spring容器中實例默認是單例的,transactionManager也一樣,那麼事務在處理時單例的manager是如何控制每個線程的事務要如何處理呢,這裏面就應用了大量的ThreadLocal。

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