首先,ThreadLocal 不是用來解決共享對象的多線程訪問問題的,通常狀況下,經過ThreadLocal.set() 到線程中的對象是該線程本身使用的對象,其餘線程是不須要訪問的,也訪問不到的。各個線程中訪問的是不一樣的對象。
另外,說ThreadLocal使得各線程可以保持各自獨立的一個對象,並非經過ThreadLocal.set()來實現的,而是經過每一個線程中的new 對象 的操做來建立的對象,每一個線程建立一個,不是什麼對象的拷貝或副本。經過ThreadLocal.set()將這個新建立的對象的引用保存到各線程的本身的一個map中,每一個線程都有這樣一個map,執行ThreadLocal.get()時,各線程從本身的map中取出放進去的對象,所以取出來的是各自本身線程中的對象,ThreadLocal實例是做爲map的key來使用的。
若是ThreadLocal.set()進去的東西原本就是多個線程共享的同一個對象,那麼多個線程的ThreadLocal.get()取得的仍是這個共享對象自己,仍是有併發訪問問題。
下面來看一個hibernate中典型的ThreadLocal的應用:
- private static final ThreadLocal threadSession = new ThreadLocal();
-
- public static Session getSession() throws InfrastructureException {
- Session s = (Session) threadSession.get();
- try {
- if (s == null) {
- s = getSessionFactory().openSession();
- threadSession.set(s);
- }
- } catch (HibernateException ex) {
- throw new InfrastructureException(ex);
- }
- return s;
- }
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
能夠看到在getSession()方法中,首先判斷當前線程中有沒有放進去session,若是尚未,那麼經過sessionFactory().openSession()來建立一個session,再將session set到線程中,實際是放到當前線程的ThreadLocalMap這個map中,這時,對於這個session的惟一引用就是當前線程中的那個ThreadLocalMap(下面會講到),而threadSession做爲這個值的key,要取得這個session能夠經過threadSession.get()來獲得,裏面執行的操做實際是先取得當前線程中的ThreadLocalMap,而後將threadSession做爲key將對應的值取出。這個session至關於線程的私有變量,而不是public的。
顯然,其餘線程中是取不到這個session的,他們也只能取到本身的ThreadLocalMap中的東西。要是session是多個線程共享使用的,那還不亂套了。
試想若是不用ThreadLocal怎麼來實現呢?可能就要在action中建立session,而後把session一個個傳到service和dao中,這可夠麻煩的。或者能夠本身定義一個靜態的map,將當前thread做爲key,建立的session做爲值,put到map中,應該也行,這也是通常人的想法,但事實上,ThreadLocal的實現恰好相反,它是在每一個線程中有一個map,而將ThreadLocal實例做爲key,這樣每一個map中的項數不多,並且當線程銷燬時相應的東西也一塊兒銷燬了,不知道除了這些還有什麼其餘的好處。
總之,ThreadLocal不是用來解決對象共享訪問問題的,而主要是提供了保持對象的方法和避免參數傳遞的方便的對象訪問方式。概括了兩點:
1。每一個線程中都有一個本身的ThreadLocalMap類對象,能夠將線程本身的對象保持到其中,各管各的,線程能夠正確的訪問到本身的對象。
2。將一個共用的ThreadLocal靜態實例做爲key,將不一樣對象的引用保存到不一樣線程的ThreadLocalMap中,而後在線程執行的各處經過這個靜態ThreadLocal實例的get()方法取得本身線程保存的那個對象,避免了將這個對象做爲參數傳遞的麻煩。
固然若是要把原本線程共享的對象經過ThreadLocal.set()放到線程中也能夠,能夠實現避免參數傳遞的訪問方式,可是要注意get()到的是那同一個共享對象,併發訪問問題要靠其餘手段來解決。但通常來講線程共享的對象經過設置爲某類的靜態變量就能夠實現方便的訪問了,彷佛不必放到線程中。
ThreadLocal的應用場合,我以爲最適合的是按線程多實例(每一個線程對應一個實例)的對象的訪問,而且這個對象不少地方都要用到。
下面來看看ThreadLocal的實現原理(jdk1.5源碼)
- public class ThreadLocal<T> {
- /**
- * ThreadLocals rely on per-thread hash maps attached to each thread
- * (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
- * objects act as keys, searched via threadLocalHashCode. This is a
- * custom hash code (useful only within ThreadLocalMaps) that eliminates
- * collisions in the common case where consecutively constructed
- * ThreadLocals are used by the same threads, while remaining well-behaved
- * in less common cases.
- */
- private final int threadLocalHashCode = nextHashCode();
-
- /**
- * The next hash code to be given out. Accessed only by like-named method.
- */
- private static int nextHashCode = 0;
-
- /**
- * The difference between successively generated hash codes - turns
- * implicit sequential thread-local IDs into near-optimally spread
- * multiplicative hash values for power-of-two-sized tables.
- */
- private static final int HASH_INCREMENT = 0x61c88647;
-
- /**
- * Compute the next hash code. The static synchronization used here
- * should not be a performance bottleneck. When ThreadLocals are
- * generated in different threads at a fast enough rate to regularly
- * contend on this lock, memory contention is by far a more serious
- * problem than lock contention.
- */
- private static synchronized int nextHashCode() {
- int h = nextHashCode;
- nextHashCode = h + HASH_INCREMENT;
- return h;
- }
-
- /**
- * Creates a thread local variable.
- */
- public ThreadLocal() {
- }
-
- /**
- * Returns the value in the current thread's copy of this thread-local
- * variable. Creates and initializes the copy if this is the first time
- * the thread has called this 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)
- return (T)map.get(this);
-
- // Maps are constructed lazily. if the map for this thread
- // doesn't exist, create it, with this ThreadLocal and its
- // initial value as its only entry.
- T value = initialValue();
- createMap(t, value);
- return value;
- }
-
- /**
- * Sets the current thread's copy of this thread-local variable
- * to the specified value. Many applications will have no need for
- * this functionality, relying solely on the {@link #initialValue}
- * method to set the values of thread-locals.
- *
- * @param value the value to be stored in the current threads' 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);
- }
-
- .......
-
- /**
- * 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 {
-
- ........
-
- }
-
- }
public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread hash maps attached to each thread
* (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
* objects act as keys, searched via threadLocalHashCode. This is a
* custom hash code (useful only within ThreadLocalMaps) that eliminates
* collisions in the common case where consecutively constructed
* ThreadLocals are used by the same threads, while remaining well-behaved
* in less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Accessed only by like-named method.
*/
private static int nextHashCode = 0;
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Compute the next hash code. The static synchronization used here
* should not be a performance bottleneck. When ThreadLocals are
* generated in different threads at a fast enough rate to regularly
* contend on this lock, memory contention is by far a more serious
* problem than lock contention.
*/
private static synchronized int nextHashCode() {
int h = nextHashCode;
nextHashCode = h + HASH_INCREMENT;
return h;
}
/**
* Creates a thread local variable.
*/
public ThreadLocal() {
}
/**
* Returns the value in the current thread's copy of this thread-local
* variable. Creates and initializes the copy if this is the first time
* the thread has called this 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)
return (T)map.get(this);
// Maps are constructed lazily. if the map for this thread
// doesn't exist, create it, with this ThreadLocal and its
// initial value as its only entry.
T value = initialValue();
createMap(t, value);
return value;
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Many applications will have no need for
* this functionality, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current threads' 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);
}
.......
/**
* 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 {
........
}
}
能夠看到ThreadLocal類中的變量只有這3個int型:
- private final int threadLocalHashCode = nextHashCode();
- private static int nextHashCode = 0;
- private static final int HASH_INCREMENT = 0x61c88647;
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode = 0;
private static final int HASH_INCREMENT = 0x61c88647;
而做爲ThreadLocal實例的變量只有 threadLocalHashCode 這一個,nextHashCode 和HASH_INCREMENT 是ThreadLocal類的靜態變量,實際上HASH_INCREMENT是一個常量,表示了連續分配的兩個ThreadLocal實例的threadLocalHashCode值的增量,而nextHashCode 的表示了即將分配的下一個ThreadLocal實例的threadLocalHashCode 的值。
能夠來看一下建立一個ThreadLocal實例即new ThreadLocal()時作了哪些操做,從上面看到構造函數ThreadLocal()裏什麼操做都沒有,惟一的操做是這句:
- private final int threadLocalHashCode = nextHashCode();
private final int threadLocalHashCode = nextHashCode();
那麼nextHashCode()作了什麼呢:
- private static synchronized int nextHashCode() {
- int h = nextHashCode;
- nextHashCode = h + HASH_INCREMENT;
- return h;
- }
private static synchronized int nextHashCode() {
int h = nextHashCode;
nextHashCode = h + HASH_INCREMENT;
return h;
}
就是將ThreadLocal類的下一個hashCode值即nextHashCode的值賦給實例的threadLocalHashCode,而後nextHashCode的值增長HASH_INCREMENT這個值。
所以ThreadLocal實例的變量只有這個threadLocalHashCode,並且是final的,用來區分不一樣的ThreadLocal實例,ThreadLocal類主要是做爲工具類來使用,那麼ThreadLocal.set()進去的對象是放在哪兒的呢?
看一下上面的set()方法,兩句合併一下成爲
- ThreadLocalMap map = Thread.currentThread().threadLocals;
ThreadLocalMap map = Thread.currentThread().threadLocals;
這個ThreadLocalMap 類是ThreadLocal中定義的內部類,可是它的實例卻用在Thread類中:
- public class Thread implements Runnable {
- ......
-
- /* ThreadLocal values pertaining to this thread. This map is maintained
- * by the ThreadLocal class. */
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ......
- }
public class Thread implements Runnable {
......
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
......
}
再看這句:
- if (map != null)
- map.set(this, value);
if (map != null)
map.set(this, value);
也就是將該ThreadLocal實例做爲key,要保持的對象做爲值,設置到當前線程的ThreadLocalMap 中,get()方法一樣你們看了代碼也就明白了,ThreadLocalMap 類的代碼太多了,我就不帖了,本身去看源碼吧。 java
深刻研究java.lang.ThreadLocal類
1、概述
ThreadLocal是什麼呢?其實ThreadLocal並不是是一個線程的本地實現版本,它並非一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名爲ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用很是簡單,就是爲每個使用該變量的線程都提供一個變量值的副本,是Java中一種較爲特殊的線程綁定機制,是每個線程均可以獨立地改變本身的副本,而不會和其它線程的副本衝突。
從線程的角度看,每一個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的而且 ThreadLocal 實例是可訪問的;在線程消失以後,其線程局部實例的全部副本都會被垃圾回收(除非存在對這些副本的其餘引用)。
經過ThreadLocal存取的數據,老是與當前線程相關,也就是說,JVM 爲每一個運行的線程,綁定了私有的本地實例存取空間,從而爲多線程環境常出現的併發訪問問題提供了一種隔離機制。
ThreadLocal是如何作到爲每個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於存儲每個線程的變量的副本。
歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。
2、API說明
ThreadLocal()
建立一個線程本地變量。
T get()
返回此線程局部變量的當前線程副本中的值,若是這是線程第一次調用該方法,則建立並初始化此副本。
protected T initialValue()
返回此線程局部變量的當前線程的初始值。最多在每次訪問線程來得到每一個線程局部變量時調用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。若是線程先於 get 方法調用 set(T) 方法,則不會在線程中再調用 initialValue 方法。
若該實現只返回 null;若是程序員但願將線程局部變量初始化爲 null 之外的某個值,則必須爲 ThreadLocal 建立子類,並重寫此方法。一般,將使用匿名內部類。initialValue 的典型實現將調用一個適當的構造方法,並返回新構造的對象。
void remove()
移除此線程局部變量的值。這可能有助於減小線程局部變量的存儲需求。若是再次訪問此線程局部變量,那麼在默認狀況下它將擁有其 initialValue。
void set(T value)
將此線程局部變量的當前線程副本中的值設置爲指定值。許多應用程序不須要這項功能,它們只依賴於 initialValue() 方法來設置線程局部變量的值。
在程序中通常都重寫initialValue方法,以給定一個特定的初始值。
3、典型實例
一、Hiberante的Session 工具類HibernateUtil
這個類是Hibernate官方文檔中HibernateUtil類,用於session管理。
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定義SessionFactory
static {
try {
// 經過默認配置文件hibernate.cfg.xml建立SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失敗!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//建立線程局部變量session,用來保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 獲取當前線程中的Session
*
@return Session
*
@throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 若是Session尚未打開,則新開一個Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //將新開的Session保存到線程局部變量中
}
return s;
}
public static void closeSession() throws HibernateException {
//獲取線程局部變量,並強制轉換爲Session類型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
在這個類中,因爲沒有重寫ThreadLocal的initialValue()方法,則首次建立線程局部變量session其初始值爲null,第一次調用currentSession()的時候,線程局部變量的get()方法也爲null。所以,對session作了判斷,若是爲null,則新開一個Session,並保存到線程局部變量session中,這一步很是的關鍵,這也是「public static final ThreadLocal session = new ThreadLocal()」所建立對象session能強制轉換爲Hibernate Session對象的緣由。
二、另一個實例
建立一個Bean,經過不一樣的線程對象設置Bean屬性,保證各個線程Bean對象的獨立性。
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2007-11-23
* Time: 10:45:02
* 學生
*/
public class Student {
private int age = 0; //年齡
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2007-11-23
* Time: 10:53:33
* 多線程下測試程序
*/
public class ThreadLocalDemo implements Runnable {
//建立線程局部變量studentLocal,在後面你會發現用來保存Student對象
private final static ThreadLocal studentLocal = new ThreadLocal();
public static void main(String[] agrs) {
ThreadLocalDemo td = new ThreadLocalDemo();
Thread t1 = new Thread(td, "a");
Thread t2 = new Thread(td, "b");
t1.start();
t2.start();
}
public void run() {
accessStudent();
}
/**
* 示例業務方法,用來測試
*/
public void accessStudent() {
//獲取當前線程的名字
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
//產生一個隨機數並打印
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread " + currentThreadName + " set age to:" + age);
//獲取一個Student對象,並將隨機數年齡插入到對象屬性中
Student student = getStudent();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(500);
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
protected Student getStudent() {
//獲取本地線程變量並強制轉換爲Student類型
Student student = (Student) studentLocal.get();
//線程首次執行此方法的時候,studentLocal.get()確定爲null
if (student == null) {
//建立一個Student對象,並保存到本地線程變量studentLocal中
student = new Student();
studentLocal.set(student);
}
return student;
}
}
運行結果:
a is running!
thread a set age to:76
b is running!
thread b set age to:27
thread a first read age is:76
thread b first read age is:27
thread a second read age is:76
thread b second read age is:27
能夠看到a、b兩個線程age在不一樣時刻打印的值是徹底相同的。這個程序經過妙用ThreadLocal,既實現多線程併發,遊兼顧數據的安全性。
4、總結
ThreadLocal使用場合主要解決多線程中數據數據因併發產生不一致問題。ThreadLocal爲每一個線程的中併發訪問的數據提供一個副本,經過訪問副原本運行業務,這樣的結果是耗費了內存,單大大減小了線程同步所帶來性能消耗,也減小了線程併發控制的複雜度。
ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。
ThreadLocal和Synchonized都用於解決多線程併發訪問。可是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal爲每個線程都提供了變量的副本,使得每一個線程在某一時間訪問到的並非同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用於在多個線程間通訊時可以得到數據共享。
Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。
固然ThreadLocal並不能替代synchronized,它們處理不一樣的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。
5、ThreadLocal使用的通常步驟
一、在多線程的類(如ThreadDemo類)中,建立一個ThreadLocal對象threadXxx,用來保存線程間須要隔離處理的對象xxx。
二、在ThreadDemo類中,建立一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象爲null時候,應該new()一個隔離訪問類型的對象,並強制轉換爲要應用的類型。
三、在ThreadDemo類的run()方法中,經過getXxx()方法獲取要操做的數據,這樣能夠保證每一個線程對應一個數據對象,在任什麼時候刻都操做的是這個對象。
ThreadLocal是什麼 程序員
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。使用這個工具類能夠很簡潔地編寫出優美的多線程程序。 sql
ThreadLocal很容易讓人望文生義,想固然地認爲是一個「本地線程」。其實,ThreadLocal並非一個Thread,而是Thread的局部變量,也許把它命名爲ThreadLocalVariable更容易讓人理解一些。 安全
當使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。 session
從線程的角度看,目標變量就象是線程的本地變量,這也是類名中「Local」所要表達的意思。 多線程
線程局部變量並非Java的新發明,不少語言(如IBM IBM XL FORTRAN)在語法層面就提供線程局部變量。在Java中沒有提供在語言級支持,而是變相地經過ThreadLocal的類提供支持。 併發
因此,在Java中編寫線程局部變量的代碼相對來講要笨拙一些,所以形成線程局部變量沒有在Java開發者中獲得很好的普及。 app
ThreadLocal的接口方法 less
ThreadLocal類接口很簡單,只有4個方法,咱們先來了解一下: dom
設置當前線程的線程局部變量的值。
該方法返回當前線程所對應的線程局部變量。
將當前線程局部變量的值刪除,目的是爲了減小內存的佔用,該方法是JDK 5.0新增的方法。須要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。
- protected Object initialValue()
返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,而且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。
值得一提的是,在JDK5.0中,ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。
ThreadLocal是如何作到爲每個線程維護變量的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於存儲每個線程的變量副本,Map中元素的鍵爲線程對象,而值對應線程的變量副本。咱們本身就能夠提供一個簡單的實現版本:
代碼清單1 SimpleThreadLocal
public class SimpleThreadLocal {
private Map valueMap = Collections.synchronizedMap(new HashMap());
public void set(Object newValue) {
valueMap.put(Thread.currentThread(), newValue);①鍵爲線程對象,值爲本線程的變量副本
}
public Object get() {
Thread currentThread = Thread.currentThread();
Object o = valueMap.get(currentThread);②返回本線程對應的變量
if (o == null && !valueMap.containsKey(currentThread)) {③若是在Map中不存在,放到Map
中保存起來。
o = initialValue();
valueMap.put(currentThread, o);
}
return o;
}
public void remove() {
valueMap.remove(Thread.currentThread());
}
public Object initialValue() {
return null;
}
}
雖然代碼清單9‑3這個ThreadLocal實現版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實現思路上是相近的。
一個TheadLocal實例
下面,咱們經過一個具體的實例瞭解一下ThreadLocal的具體使用方法。
代碼清單2 SequenceNumber
package com.baobaotao.basic;
public class SequenceNumber {
①經過匿名內部類覆蓋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)
{
SequenceNumber sn = new SequenceNumber();
③ 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 SequenceNumber sn;
public TestClient(SequenceNumber 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,它們共享同一個SequenceNumber實例。運行以上代碼,在控制檯上輸出如下的結果:
thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-2] sn[2]
thread[Thread-0] sn[2]
thread[Thread-1] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[3]
thread[Thread-1] sn[3]
考察輸出的結果信息,咱們發現每一個線程所產生的序號雖然都共享同一個SequenceNumber實例,但它們並無發生相互干擾的狀況,而是各自產生獨立的序列號,這是由於咱們經過ThreadLocal爲每個線程提供了單獨的副本。
Thread同步機制的比較
ThreadLocal和線程同步機制相比有什麼優點呢?ThreadLocal和線程同步機制都是爲了解決多線程中相同變量的訪問衝突問題。
在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機制要求程序慎密地分析何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放對象鎖等繁雜的問題,程序設計和編寫難度相對較大。
而ThreadLocal則從另外一個角度來解決多線程的併發訪問。ThreadLocal會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。由於每個線程都擁有本身的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時,能夠把不安全的變量封裝進ThreadLocal。
因爲ThreadLocal中能夠持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,須要強制類型轉換。但JDK 5.0經過泛型很好的解決了這個問題,在必定程度地簡化ThreadLocal的使用,代碼清單 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。
歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。
Spring使用ThreadLocal解決線程安全問題
咱們知道在通常狀況下,只有無狀態的Bean才能夠在多線程環境下共享,在Spring中,絕大部分Bean均可以聲明爲singleton做用域。就是由於Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態採用ThreadLocal進行處理,讓它們也成爲線程安全的狀態,由於有狀態的Bean就能夠在多線程中共享了。
通常的Web應用劃分爲展示層、服務層和持久層三個層次,在不一樣的層中編寫對應的邏輯,下層經過接口向上層開放功能調用。在通常狀況下,從接收請求到返回響應所通過的全部程序調用都同屬於一個線程,如圖9‑2所示:
圖1同一線程貫通三層
這樣你就能夠根據須要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,全部關聯的對象引用到的都是同一個變量。
下面的實例可以體現Spring對有狀態Bean的改造思路:
代碼清單3 TopicDao:非線程安全
public class TopicDao {
private Connection conn;①一個非線程安全的變量
public void addTopic(){
Statement stat = conn.createStatement();②引用非線程安全變量
…
}
}
因爲①處的conn是成員變量,由於addTopic()方法是非線程安全的,必須在使用時建立一個新TopicDao實例(非singleton)。下面使用ThreadLocal對conn這個非線程安全的「狀態」進行改造:
代碼清單4 TopicDao:線程安全
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
①使用ThreadLocal保存Connection變量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
②若是connThreadLocal沒有本線程對應的Connection建立一個新的Connection,
並將其保存到線程本地變量中。
if (connThreadLocal.get() == null) {
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
return connThreadLocal.get();③直接返回線程本地變量
}
}
public void addTopic() {
④從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。
小結
ThreadLocal是解決線程安全問題一個很好的思路,它經過爲每一個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題。在不少狀況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的併發性。