線程安全問題的由來java
在傳統的Web開發中,咱們處理Http請求最經常使用的方式是經過實現Servlet對象來進行Http請求的響應.Servlet是J2EE的重要標準之一,規定了Java如何響應Http請求的規範.經過HttpServletRequest和HttpServletResponse對象,咱們可以輕鬆地與Web容器交互.編程
當Web容器收到一個Http請求時,Web容器中的一個主調度線程會從事先定義好的線程中分配一個當前工做線程,將請求分配給當前的工做線程,由該線程來執行對應的Servlet對象中的service方法.當這個工做線程正在執行的時候,Web容器收到另一個請求,主調度線程會一樣從線程池中選擇另一個工做線程來服務新的請求.Web容器自己並不關心這個新的請求是否訪問的是同一個Servlet實例.所以,咱們能夠得出一個結論:對於同一個Servlet對象的多個請求,Servlet的service方法將在一個多線程的環境中併發執行.因此,Web容器默認採用單實例(單Servlet實例)多線程的方式來處理Http請求.這種處理方式可以減小新建Servlet實例的開銷,從而縮短了對Http請求的響應時間.可是,這樣的處理方式會致使變量訪問的線程安全問題.也就是說,Servlet對象並非一個線程安全的對象.安全
01.package com.qingdao.proxy; 02. 03.public class ThreadSafeTestServlet extends HttpServlet { 04. // 定義一個實例變量,並不是一個線程安全的變量 05. private int counter = 0; 06. 07. public void doGet(HttpServletRequest req, HttpServletResponse resp) 08. throws ServletException, IOException { 09. doPost(req, resp); 10. } 11. 12. public void doPost(HttpServletRequest req, HttpServletResponse resp) 13. throws ServletException, IOException { 14. // 輸出當前Servlet的信息以及當前線程的信息 15. System.out.println(this + ":" + Thread.currentThread()); 16. // 循環,並增長實例變量counter的值 17. for (int i = 0; i < 5; i++) { 18. System.out.println("Counter = " + counter); 19. try { 20. Thread.sleep((long) Math.random() * 1000); 21. counter++; 22. } catch (InterruptedException exc) { 23. } 24. } 25. } 26.}
sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor23,5,main]
Counter = 60
Counter = 61
Counter = 62
Counter = 65
Counter = 68
Counter = 71
Counter = 74
Counter = 77
Counter = 80
Counter = 83
sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor22,5,main]
Counter = 61
Counter = 63
Counter = 66
Counter = 69
Counter = 72
Counter = 75
Counter = 78
Counter = 81
Counter = 84
Counter = 87
sample.SimpleServlet@11e1bbf:Thread[http-8081-Processor24,5,main]
Counter = 61
Counter = 64
Counter = 67
Counter = 70
Counter = 73
Counter = 76
Counter = 79
Counter = 82
Counter = 85
Counter = 88
01.public class Thread implements Runnable { 02. // 這裏省略了許多其餘的代碼 03. ThreadLocal.ThreadLocalMap threadLocals = null; 04.}
這是JDK中Thread源碼的一部分,從中咱們能夠看出ThreadLocalMap跟隨着當前的線程而存在.不一樣的線程Thread,擁有不一樣的ThreadLocalMap的本地實例變量,這也就是「副本」的含義.接下來咱們再來看看ThreadLocal.ThreadLocalMap是如何定義的,以及ThreadLocal如何來操做它數據結構
01.public class ThreadLocal<T> { 02. 03. // 這裏省略了許多其餘代碼 04. 05. // 將value的值保存於當前線程的本地變量中 06. public void set(T value) { 07. // 獲取當前線程 08. Thread t = Thread.currentThread(); 09. // 調用getMap方法得到當前線程中的本地變量ThreadLocalMap 10. ThreadLocalMap map = getMap(t); 11. // 若是ThreadLocalMap已存在,直接使用 12. if (map != null) 13. // 以當前的ThreadLocal的實例做爲key,存儲於當前線程的 14. // ThreadLocalMap中,若是當前線程中被定義了多個不一樣的ThreadLocal 15. // 的實例,則它們會做爲不一樣key進行存儲而不會互相干擾 16. map.set(this, value); 17. else 18. // ThreadLocalMap不存在,則爲當前線程建立一個新的 19. createMap(t, value); 20. } 21. 22. // 獲取當前線程中以當前ThreadLocal實例爲key的變量值 23. public T get() { 24. // 獲取當前線程 25. Thread t = Thread.currentThread(); 26. // 獲取當前線程中的ThreadLocalMap 27. ThreadLocalMap map = getMap(t); 28. if (map != null) { 29. // 獲取當前線程中以當前ThreadLocal實例爲key的變量值 30. ThreadLocalMap.Entry e = map.getEntry(this); 31. if (e != null) 32. return (T) e.value; 33. } 34. // 當map不存在時,設置初始值 35. return setInitialValue(); 36. } 37. 38. // 從當前線程中獲取與之對應的ThreadLocalMap 39. ThreadLocalMap getMap(Thread t) { 40. return t.threadLocals; 41. } 42. 43. // 建立當前線程中的ThreadLocalMap 44. void createMap(Thread t, T firstValue) { 45. // 調用構造函數生成當前線程中的ThreadLocalMap 46. t.threadLocals = new ThreadLocalMap(this, firstValue); 47. } 48. 49. // ThreadLoaclMap的定義 50. static class ThreadLocalMap { 51. // 這裏省略了許多代碼 52. } 53.}
深刻比較TheadLocal模式與synchronized關鍵字多線程
ThreadLocal模式synchronized關鍵字都用於處理多線程併發訪問變量的問題,只是兩者處理問題的角度和思路不一樣.併發
1)ThreadLocal是一個java類,經過對當前線程中的局部變量的操做來解決不一樣線程的變量訪問的衝突問題.因此,ThreadLocal提供了線程安全的共享對象機制,每一個線程都擁有其副本.dom
2)Java中的synchronized是一個保留字,它依靠JVM的鎖機制來實現臨界區的函數或者變量的訪問中的原子性.在同步機制中,經過對象的鎖機制保證同一時間只有一個線程訪問變量.此時,被用做「鎖機制」的變量時多個線程共享的.函數
同步機制(synchronized關鍵字)採用了「以時間換空間」的方式,提供一份變量,讓不一樣的線程排隊訪問.而ThreadLocal採用了「以空間換時間」的方式,爲每個線程都提供一份變量的副本,從而實現同時訪問而互不影響測試
ThreadLocal模式的核心元素this
要完成ThreadLocal模式,其中最關鍵的地方就是建立一個任何地方均可以訪問到的ThreadLocal實例(也就是執行示意圖中的菱形部分).而這一點,咱們能夠經過類的靜態實例變量來實現,這個用於承載靜態實例變量的類就被視做是一個共享環境.咱們來看一個例子,如代碼清單以下所示:
01.public class Counter { 02. //新建一個靜態的ThreadLocal變量,並經過get方法將其變爲一個可訪問的對象 03. private static ThreadLocal<Integer> counterContext = new ThreadLocal<Integer>(){ 04. protected synchronized Integer initialValue(){ 05. return 10; 06. } 07. }; 08. // 經過靜態的get方法訪問ThreadLocal中存儲的值 09. public static Integer get(){ 10. return counterContext.get(); 11. } 12. // 經過靜態的set方法將變量值設置到ThreadLocal中 13. public static void set(Integer value) { 14. counterContext.set(value); 15. } 16. // 封裝業務邏輯,操做存儲於ThreadLocal中的變量 17. public static Integer getNextCounter() { 18. counterContext.set(counterContext.get() + 1); 19. return counterContext.get(); 20. } 21. }
01.public class ThreadLocalTest extends Thread { 02. public void run(){ 03. for(int i = 0; i < 3; i++){ 04. System.out.println("Thread[" + Thread.currentThread().getName() + "],counter=" + Counter.getNextCounter()); 05. } 06. } 07. }
01.public class Test { 02. public static void main(String[] args) { 03. ThreadLocalTest testThread1 = new ThreadLocalTest(); 04. ThreadLocalTest testThread2 = new ThreadLocalTest(); 05. ThreadLocalTest testThread3 = new ThreadLocalTest(); 06. testThread1.start(); 07. testThread2.start(); 08. testThread3.start(); 09. } 10. }
輸出結果:
上面的輸出結果也證明了,counter的值在多線程環境中的訪問是線程安全的.從對例子的分析中咱們能夠再次體會到,ThreadLocal模式最合適的使用場景:在同一個線程(Thread)的不一樣開發層次中共享數據.
從上面的例子中,咱們能夠簡單總結出實現ThreadLocal模式的兩個主要步驟:
1. 創建一個類,並在其中封裝一個靜態的ThreadLocal變量,使其成爲一個共享數據環境.
2. 在類中實現訪問靜態ThreadLocal變量的靜態方法(設值和取值).
創建在ThreadLocal模式的實現步驟之上,ThreadLocal的使用則更加簡單.在線程執行的任何地方,咱們均可以經過訪問共享數據類中所提供的ThreadLocal變量的設值和取值方法安全地得到當前線程中安全的變量值.
這兩個步驟,咱們以後會在Struts2的實現中屢次說起,讀者只要能充分理解ThreadLocal處理多線程訪問的基本原理,就能對Struts2的數據訪問和數據共享的設計有一個總體的認識.
講到這裏,咱們回過頭來看看ThreadLocal模式的引入,到底對咱們的編程模型有什麼重要的意義呢?
結論 :使用ThreadLocal模式,可使得數據在不一樣的編程層次獲得有效地共享,
這一點,是由ThreadLocal模式的實現機理決定的.由於實現ThreadLocal模式的一個重要步驟,就是構建一個靜態的共享存儲空間.從而使得任何對象在任什麼時候刻均可以安全地對數據進行訪問.
結論 使用ThreadLocal模式,能夠對執行邏輯與執行數據進行有效解耦
這一點是ThreadLocal模式給咱們帶來的最爲核心的一個影響,由於在通常狀況下,Java對象之間的協做關係,主要經過參數和返回值來進行消息傳遞,這也是對象協做之間的一個重要依賴,而ThreadLocal模式完全打破了這種依賴關係,經過線程安全的共享對象來進行數據共享,能夠有效避免在編程層次之間造成數據依賴,這也成爲了XWork事件處理體系設計的核心.
ThreadLocal 多線程共享變量 的隔離
線程內部有ThreadLocalMap 屬性 ,而ThreadLocalMap是ThreadLocal的靜態內部類
橫向 線程與線程之間經過Thread線程內部ThradLocalMap 隔離;
縱向 變量與變量之間經過不一樣ThreadLocal對象隔離
ThreadLocalMap 內部以this指針根據當前ThreadLocal對象爲鍵存儲,實現多個變量的隔離