Spring經過各類模板類下降了開發者使用各類數據持久技術的難度。這些模板類都是線程安全的,也就是說,多個DAO能夠複用同一個模板實例而不會發生衝突。咱們使用模板類訪問底層數據,根據持久化技術的不一樣,模板類須要綁定數據鏈接或會話的資源。但這些資源自己是非線程安全的,也就是說它們不能在同一時刻被多個線程共享。雖然模板類經過資源池獲取數據鏈接或會話,但資源池自己解決的是數據鏈接或會話的緩存問題,並不是數據鏈接或會話的線程安全問題。
按照傳統經驗,若是某個對象是非線程安全的,在多線程環境下,對對象的訪問必須採用synchronized進行線程同步。但模板類並未採用線程同步機制,由於線程同步會下降併發性,影響系統性能。此外,經過代碼同步解決線程安全的挑戰性很大,可能會加強好幾倍的實現難度。那麼模板類究竟仰仗何種魔法神功,能夠在無須線程同步的狀況下就化解線程安全的難題呢?答案就是ThreadLocal!
ThreadLocal在Spring中發揮着重要的做用,在管理request做用域的Bean、事務管理、任務調度、AOP等模塊都出現了它們的身影,起着舉足輕重的做用。java
ThreadLocal是什麼
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。使用這個工具類能夠很簡潔地編寫出優美的多線程程序。
ThreadLocal,顧名思義,它不是一個線程,而是線程的一個本地化對象。當工做於多線程中的對象使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程分配一個獨立的變量副本。因此每個線程均可以獨立地改變本身的副本,而不會影響其餘線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中「Local」所要表達的意思。
線程局部變量並非Java的新發明,不少語言(如IBM XL、FORTRAN)在語法層面就提供線程局部變量。在Java中沒有提供語言級支持,而以一種變通的方法,經過ThreadLocal的類提供支持。因此,在Java中編寫線程局部變量的代碼相對來講要笨拙一些,這也是爲何線程局部變量沒有在Java開發者中獲得很好普及的緣由。 緩存
ThreadLocal的接口方法
ThreadLocal類接口很簡單,只有4個方法,咱們先來了解一下。 安全
值得一提的是,在JDK5.0中,ThreadLocal已經支持泛型,該類的類名已經變爲ThreadLocal<T>。API方法也相應進行了調整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。
ThreadLocal是如何作到爲每個線程維護變量的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於存儲每個線程的變量副本,Map中元素的鍵爲線程對象,而值對應線程的變量副本。多線程
一個TheadLocal實例
咱們經過一個具體的實例瞭解一下ThreadLocal的具體使用方法。
併發
package cn.chinotan.service.thredLocal; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.concurrent.*; /** * @program: test * @description: thredLocalDemo * @author: xingcheng * @create: 2018-09-09 17:13 **/ public class ThreadLocalDemo { static ThreadLocal<Integer> count = new ThreadLocal<Integer>(){ // 進行初始化 @Override protected Integer initialValue() { return 0; } }; static Integer countRef = 0; private Integer getNextCount() { count.set(count.get() + 1); return count.get(); } private Integer getNextCountRef() { countRef = countRef + 1; return countRef; } public static void main(String[] args) { ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); ExecutorService executorService = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), namedThreadFactory); ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(); executorService.execute(new CountRun(threadLocalDemo)); executorService.execute(new CountRun(threadLocalDemo)); executorService.execute(new CountRun(threadLocalDemo)); // 參照 ThreadLocalDemo threadLocalDemoRef = new ThreadLocalDemo(); executorService.execute(new CountRunRef(threadLocalDemoRef)); executorService.execute(new CountRunRef(threadLocalDemoRef)); executorService.execute(new CountRunRef(threadLocalDemoRef)); executorService.shutdown(); } static class CountRun implements Runnable{ private ThreadLocalDemo demo; public CountRun(ThreadLocalDemo dem) { demo = dem; } @Override public void run() { for(int i = 0; i < 3; i++){ System.out.println("thread[" + Thread.currentThread().getName()+ "] count [" + demo.getNextCount() + "]"); } } } static class CountRunRef extends Thread{ private ThreadLocalDemo demo; public CountRunRef(ThreadLocalDemo dem) { demo = dem; } @Override public void run() { for(int i = 0; i < 3; i++){ System.out.println("threadRef[" + Thread.currentThread().getName()+ "] count [" + demo.getNextCountRef() + "]"); } } } }
輸出:ide
考查輸出的結果信息,咱們發現每一個線程所產生的序號雖然都共享同一個Sequence Number實例,但它們並無發生相互干擾的狀況,而是各自產生獨立的序列號,這是由於咱們經過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就可以以singleton的方式在多線程中正常工做了。工具
通常的Web應用劃分爲展示層、服務層和持久層三個層次,在不一樣的層中編寫對應的邏輯,下層經過接口向上層開放功能調用。在通常狀況下,從接收請求到返回響應所通過的全部程序調用都同屬於一個線程,這樣用戶就能夠根據須要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,全部對象所訪問的同一ThreadLocal變量都是當前線程所綁定的。 性能