早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。使用這個工具類能夠很簡潔地編寫出優美的多線程程序。當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。從線程的角度看,目標變量就象是線程的本地變量,這也是類名中「Local」所要表達的意思。因此,在Java中編寫線程局部變量的代碼相對來講要笨拙一些,所以形成線程局部變量沒有在Java開發者中獲得很好的普及。引自@楓之逆 人們常說,鎖是一種以時間換空間的機制,而ThreadLocal正好是以空間換時間的。 java
如下是轉載自@楓之逆的博文: mysql
ThreadLocal類接口很簡單,只有4個方法,咱們先來了解一下: sql
值得一提的是,在JDK5.0中,ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。ThreadLocal是如何作到爲每個線程維護變量的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於存儲每個線程的變量副本,Map中元素的鍵爲線程對象,而值對應線程的變量副本。咱們本身就能夠提供一個簡單的實現版本: 安全
package com.test; public class TestNum { // ①經過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() { public Integer initialValue() { return 0; } }; // ②獲取下一個序列值 public int getNextNum() { seqNum.set(seqNum.get() + 1); return seqNum.get(); } public static void main(String[] args) { TestNum sn = new TestNum(); // ③ 3個線程共享sn,各自產生序列號 TestClient t1 = new TestClient(sn); TestClient t2 = new TestClient(sn); TestClient t3 = new TestClient(sn); t1.start(); t2.start(); t3.start(); } private static class TestClient extends Thread { private TestNum sn; public TestClient(TestNum sn) { this.sn = sn; } public void run() { for (int i = 0; i < 3; i++) { // ④每一個線程打出3個序列值 System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn[" + sn.getNextNum() + "]"); } } } }
一般咱們經過匿名內部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產生一組序列號,在③處,咱們生成3個TestClient,它們共享同一個TestNum實例。運行以上代碼,在控制檯上輸出如下的結果: 多線程
thread[Thread-0] --> sn[1]考察輸出的結果信息,咱們發現每一個線程所產生的序號雖然都共享同一個TestNum實例,但它們並無發生相互干擾的狀況,而是各自產生獨立的序列號,這是由於咱們經過ThreadLocal爲每個線程提供了單獨的副本。 併發
ThreadLocal和線程同步機制相比有什麼優點呢?ThreadLocal和線程同步機制都是爲了解決多線程中相同變量的訪問衝突問題。在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。而ThreadLocal則從另外一個角度來解決多線程的併發訪問。ThreadLocal會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。由於每個線程都擁有本身的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,能夠把不安全的變量封裝進ThreadLocal。因爲ThreadLocal中能夠持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,須要強制類型轉換。但JDK 5.0經過泛型很好的解決了這個問題,在必定程度地簡化ThreadLocal的使用,代碼清單 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。 ide
歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。Spring使用ThreadLocal解決線程安全問題咱們知道在通常狀況下,只有無狀態的Bean才能夠在多線程環境下共享,在Spring中,絕大部分Bean均可以聲明爲singleton做用域。就是由於Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態採用ThreadLocal進行處理,讓它們也成爲線程安全的狀態,由於有狀態的Bean就能夠在多線程中共享了。通常的Web應用劃分爲展示層、服務層和持久層三個層次,在不一樣的層中編寫對應的邏輯,下層經過接口向上層開放功能調用。在通常狀況下,從接收請求到返回響應所通過的全部程序調用都同屬於一個線程,以下圖所示: 工具
同一線程貫通三層這樣你就能夠根據須要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,全部關聯的對象引用到的都是同一個變量。下面的實例可以體現Spring對有狀態Bean的改造思路:代碼清單3 TestDao:非線程安全 學習
package com.test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; public class TestDao { private Connection conn;// ①一個非線程安全的變量 public void addTopic() throws SQLException { Statement stat = conn.createStatement();// ②引用非線程安全變量 // … } }
因爲①處的conn是成員變量,由於addTopic()方法是非線程安全的,必須在使用時建立一個新TopicDao實例(非singleton)。下面使用ThreadLocal對conn這個非線程安全的「狀態」進行改造:代碼清單4 TestDao:線程安全 this
package com.test; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; public class TestDaoNew { // ①使用ThreadLocal保存Connection變量 private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); public static Connection getConnection() { // ②若是connThreadLocal沒有本線程對應的Connection建立一個新的Connection, // 並將其保存到線程本地變量中。 if (connThreadLocal.get() == null) { Connection conn = getConnection(); connThreadLocal.set(conn); return conn; } else { return connThreadLocal.get();// ③直接返回線程本地變量 } } public void addTopic() throws SQLException { // ④從ThreadLocal中獲取線程對應的Connection Statement stat = getConnection().createStatement(); } }
不一樣的線程在使用TopicDao時,先判斷connThreadLocal.get()是不是null,若是是null,則說明當前線程尚未對應的Connection對象,這時建立一個Connection對象並添加到本地線程變量中;若是不爲null,則說明當前的線程已經擁有了Connection對象,直接使用就能夠了。這樣,就保證了不一樣的線程使用線程相關的Connection,而不會使用其它線程的Connection。所以,這個TopicDao就能夠作到singleton共享了。固然,這個例子自己很粗糙,將Connection的ThreadLocal直接放在DAO只能作到本DAO的多個方法共享Connection時不發生線程安全問題,但沒法和其它DAO共用同一個Connection,要作到同一事務多DAO共享同一Connection,必須在一個共同的外部類使用ThreadLocal保存Connection。ConnectionManager.java
package com.test; 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); } }
那麼到底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); }
/** * 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; } /** * 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 * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
/** * 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(); }