完全理解ThreadLocal

知其然java

synchronized這類線程同步的機制能夠解決多線程併發問題,在這種解決方案下,多個線程訪問到的,都是同一份變量的內容。爲了防止在多線程訪問的過程當中,可能會出現的併發錯誤。不得不對多個線程的訪問進行同步,這樣也就意味着,多個線程必須前後對變量的值進行訪問或者修改,這是一種以延長訪問時間來換取線程安全性的策略。mysql

而ThreadLocal類爲每個線程都維護了本身獨有的變量拷貝。每一個線程都擁有了本身獨立的一個變量,競爭條件被完全消除了,那就沒有任何須要對這些線程進行同步,它們也能最大限度的由CPU調度,併發執行。而且因爲每一個線程在訪問該變量時,讀取和修改的,都是本身獨有的那一份變量拷貝,變量被完全封閉在每一個訪問的線程中,併發錯誤出現的可能也徹底消除了。對比前一種方案,這是一種以空間來換取線程安全性的策略。sql

來看一個運用ThreadLocal來實現數據庫鏈接Connection對象線程隔離的例子。數據庫

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionManager {

	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
		@Override
		protected Connection initialValue() {
			Connection conn = null;
			try {
				conn = DriverManager.getConnection(
						"jdbc:mysql://localhost:3306/test", "username",
						"password");
			} catch (SQLException e) {
				e.printStackTrace();
			}
			return conn;
		}
	};

	public static Connection getConnection() {
		return connectionHolder.get();
	}

	public static void setConnection(Connection conn) {
		connectionHolder.set(conn);
	}
}

經過調用ConnectionManager.getConnection()方法,每一個線程獲取到的,都是和當前線程綁定的那個Connection對象,第一次獲取時,是經過initialValue()方法的返回值來設置值的。經過ConnectionManager.setConnection(Connection conn)方法設置的Connection對象,也只會和當前線程綁定。這樣就實現了Connection對象在多個線程中的徹底隔離。在Spring容器中管理多線程環境下的Connection對象時,採用的思路和以上代碼很是類似。安全

知其因此然多線程

那麼到底ThreadLocal類是如何實現這種「爲每一個線程提供不一樣的變量拷貝」的呢?先來看一下ThreadLocal的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();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

沒有什麼魔法,在這個方法內部咱們看到,首先經過getMap(Thread t)方法獲取一個和當前線程相關的ThreadLocalMap,而後將變量的值設置到這個ThreadLocalMap對象中,固然若是獲取到的ThreadLocalMap對象爲空,就經過createMap方法建立。ide

線程隔離的祕密,就在於ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(對比Map對象來理解),每一個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類經過操做每個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不一樣線程中的隔離。由於每一個線程的變量都是本身特有的,徹底不會有併發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。this

爲了加深理解,咱們接着看上面代碼中出現的getMap和createMap方法的實現:線程

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

代碼已經說的很是直白,就是獲取和設置Thread內的一個叫threadLocals的變量,而這個變量的類型就是ThreadLocalMap,這樣進一步驗證了上文中的觀點:每一個線程都有本身獨立的ThreadLocalMap對象。打開java.lang.Thread類的源代碼,咱們能獲得更直觀的證實:

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

那麼接下來再看一下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);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        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;
    }

這兩個方法的代碼告訴咱們,在獲取和當前線程綁定的值時,ThreadLocalMap對象是以this指向的ThreadLocal對象爲鍵進行查找的,這固然和前面set()方法的代碼是相呼應的。

進一步地,咱們能夠建立不一樣的ThreadLocal實例來實現多個變量在不一樣線程間的訪問隔離,爲何能夠這麼作?由於不一樣的ThreadLocal對象做爲不一樣鍵,固然也能夠在線程的ThreadLocalMap對象中設置不一樣的值了。經過ThreadLocal對象,在多線程中共享一個值和多個值的區別,就像你在一個HashMap對象中存儲一個鍵值對和多個鍵值對同樣,僅此而已。

設置到這些線程中的隔離變量,會不會致使內存泄漏呢?ThreadLocalMap對象保存在Thread對象中,當某個線程終止後,存儲在其中的線程隔離的變量,也將做爲Thread實例的垃圾被回收掉,因此徹底不用擔憂內存泄漏的問題。在多個線程中隔離的變量,光榮的生,合理的死,真是圓滿,不是麼?

最後再提一句,ThreadLocal變量的這種隔離策略,也不是任何狀況下都能使用的。若是多個線程併發訪問的對象實例只容許,也只能建立那麼一個,那就沒有別的辦法了,老老實實的使用同步機制來訪問吧。

相關文章
相關標籤/搜索