ThreadLocal是線程變量,ThreadLocal中填充的變量屬於當前線程,該變量對其餘線程而言是隔離的。ThreadLocal爲變量在每一個線程中都建立了一個副本,那麼每一個線程能夠訪問本身內部的副本變量。數據庫
它具備3個特性:數組
在不使用ThreadLocal的狀況下,變量不隔離,獲得的結果具備隨機性。安全
public class Demo { private String variable; public String getVariable() { return variable; } public void setVariable(String variable) { this.variable = variable; } public static void main(String[] args) { Demo demo = new Demo(); for (int i = 0; i < 5; i++) { new Thread(()->{ demo.setVariable(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName()+" "+demo.getVariable()); }).start(); } } }
輸出結果:多線程
Thread-2 Thread-2 Thread-4 Thread-4 Thread-1 Thread-2 Thread-0 Thread-2 Thread-3 Thread-3
在不使用ThreadLocal的狀況下,變量隔離,每一個線程有本身專屬的本地變量variable,線程綁定了本身的variable,只對本身綁定的變量進行讀寫操做。併發
public class Demo { private ThreadLocal<String> variable = new ThreadLocal<>(); public String getVariable() { return variable.get(); } public void setVariable(String variable) { this.variable.set(variable); } public static void main(String[] args) { Demo demo = new Demo(); for (int i = 0; i < 5; i++) { new Thread(()->{ demo.setVariable(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName()+" "+demo.getVariable()); }).start(); } } }
輸出結果:ide
Thread-0 Thread-0 Thread-1 Thread-1 Thread-2 Thread-2 Thread-3 Thread-3 Thread-4 Thread-4
上述需求,經過synchronized加鎖一樣也能實現。可是加鎖對性能和併發性有必定的影響,線程訪問變量只能排隊等候依次操做。TreadLocal不加鎖,多個線程能夠併發對變量進行操做。函數
public class Demo { private String variable; public String getVariable() { return variable; } public void setVariable(String variable) { this.variable = variable; } public static void main(String[] args) { Demo demo = new Demo1(); for (int i = 0; i < 5; i++) { new Thread(()->{ synchronized (Demo.class){ demo.setVariable(Thread.currentThread().getName()); System.out.println(Thread.currentThread().getName()+" "+demo.getVariable()); } }).start(); } } }
ThreadLocal和synchronized都是用於處理多線程併發訪問資源的問題。ThreadLocal是以空間換時間的思路,每一個線程都擁有一份變量的拷貝,從而實現變量隔離,互相不干擾。關注的重點是線程之間數據的相互隔離關係。synchronized是以時間換空間的思路,只提供一個變量,線程只能經過排隊訪問。關注的是線程之間訪問資源的同步性。ThreadLocal能夠帶來更好的併發性,在多線程、高併發的環境中更爲合適一些。高併發
JDBC對於事務原子性的控制能夠經過setAutoCommit(false)設置爲事務手動提交,成功後commit,失敗後rollback。在多線程的場景下,在service層開啓事務時用的connection和在dao層訪問數據庫的connection應該要保持一致,因此併發時,線程只能隔離操做自已的connection。源碼分析
解決方案1:service層的connection對象做爲參數傳遞給dao層使用,事務操做放在同步代碼塊中。性能
存在問題:傳參提升了代碼的耦合程度,加鎖下降了程序的性能。
解決方案2:當須要獲取connection對象的時候,經過ThreadLocal對象的get方法直接獲取當前線程綁定的鏈接對象使用,若是鏈接對象是空的,則去鏈接池獲取鏈接,並經過ThreadLocal對象的set方法綁定到當前線程。使用完以後調用ThreadLocal對象的remove方法解綁鏈接對象。
ThreadLocal的優點:
每一個ThreadLocal維護一個ThreadLocalMap,Map的Key是ThreadLocal實例自己,value是要存儲的值。
每一個線程內部都有一個ThreadLocalMap,Map裏面存放的是ThreadLocal對象和線程的變量副本。Thread內部的Map經過ThreadLocal對象來維護,向map獲取和設置變量副本的值。不一樣的線程,每次獲取變量值時,只能獲取本身對象的副本的值。實現了線程之間的數據隔離。
JDK1.8的設計相比於以前的設計(經過ThreadMap維護了多個線程和線程變量的對應關係,key是Thread對象,value是線程變量)的好處在於,每一個Map存儲的Entry數量變少了,線程越多鍵值對越多。如今的鍵值對的數量是由ThreadLocal的數量決定的,通常狀況下ThreadLocal的數量少於線程的數量,並且並非每一個線程都須要建立ThreadLocal變量。當Thread銷燬時,ThreadLocal也會隨之銷燬,減小了內存的使用,以前的方案中線程銷燬後,ThreadLocalMap仍然存在。
首先獲取線程,而後獲取線程的Map。若是Map不爲空則將當前ThreadLocal的引用做爲key設置到Map中。若是Map爲空,則建立一個Map並設置初始值。
首先獲取當前線程,而後獲取Map。若是Map不爲空,則Map根據ThreadLocal的引用來獲取Entry,若是Entry不爲空,則獲取到value值,返回。若是Map爲空或者Entry爲空,則初始化並獲取初始值value,而後用ThreadLocal引用和value做爲key和value建立一個新的Map。
刪除當前線程中保存的ThreadLocal對應的實體entry。
該方法的第一次調用發生在當線程經過get方法訪問線程的ThreadLocal值時。除非線程先調用了set方法,在這種狀況下,initialValue纔不會被這個線程調用。每一個線程最多調用依次這個方法。
該方法只返回一個null,若是想要線程變量有初始值須要經過子類繼承ThreadLocal的方式去重寫此方法,一般能夠經過匿名內部類的方式實現。這個方法是protected修飾的,是爲了讓子類覆蓋而設計的。
ThreadLocalMap是ThreadLocal的靜態內部類,沒有實現Map接口,獨立實現了Map的功能,內部的Entry也是獨立實現的。
與HashMap相似,初始容量默認是16,初始容量必須是2的整數冪。經過Entry類的數據table存放數據。size是存放的數量,threshold是擴容閾值。
Entry繼承自WeakReference,key是弱引用,其目的是將ThreadLocal對象的生命週期和線程生命週期解綁。
內存溢出:沒有足夠的內存供申請者提供
內存泄漏:程序中已動態分配的堆內存因爲某種緣由程序未釋放或沒法釋放,形成系統內存的浪費,致使程序運行速度減慢甚至系統崩潰等驗證後溝。內存泄漏的堆積會致使內存溢出。
弱引用:垃圾回收器一旦發現了弱引用的對象,無論內存是否足夠,都會回收它的內存。
內存泄漏的根源是ThreadLocalMap和Thread的生命週期是同樣長的。
若是在ThreadLocalMap的key使用強引用仍是沒法徹底避免內存泄漏,ThreadLocal使用完後,ThreadLocal Reference被回收,可是Map的Entry強引用了ThreadLocal,ThreadLocal就沒法被回收,由於強引用鏈的存在,Entry沒法被回收,最後會內存泄漏。
在實際狀況中,ThreadLocalMap中使用的key爲ThreadLocal的弱引用,value是強引用。若是ThreadLocal沒有被外部強引用的話,在垃圾回收的時候,key會被清理,value不會。這樣ThreadLocalMap就出現了爲null的Entry。若是不作任何措施,value永遠不會被GC回收,就會產生內存泄漏。
ThreadLocalMap中考慮到這個狀況,在set、get、remove操做後,會清理掉key爲null的記錄(將value也置爲null)。使用完ThreadLocal後最後手動調用remove方法(刪除Entry)。
也就是說,使用完ThreadLocal後,線程仍然運行,若是忘記調用remove方法,弱引用比強引用能夠多一層保障,弱引用的ThreadLocal會被回收,對應的value會在下一次ThreadLocalMap調用get、set、remove方法的時候被清除,從而避免了內存泄漏。
構造函數建立一個長隊爲16的Entry數組,而後計算firstKey的索引,存儲到table中,設置size和threshold。
firstKey.threadLocalHashCode & (INITIAL_CAPACITY-1)用來計算索引,nextHashCode是Atomicinteger類型的,Atomicinteger類是提供原子操做的Integer類,經過線程安全的方式來加減,適合高併發使用。
每次在當前值上加上一個HASH_INCREMENT值,這個值和斐波拉契數列有關,主要目的是爲了讓哈希碼能夠均勻的分佈在2的n次方的數組裏,從而儘可能的避免衝突。
當size爲2的冪次的時候,hashCode & (size - 1)至關於取模運算hashCode % size,位運算比取模更高效一些。爲了使用這種取模運算, 全部size必須是2的冪次。這樣一來,在保證索引不越界的狀況下,減小衝突的次數。
ThreadLocalMao使用了線性探測法來解決衝突。線性探測法探測下一個地址,找到空的地址則插入,若整個空間都沒有空餘地址,則產生溢出。例如:長度爲8的數組中,當前key的hash值是6,6的位置已經被佔用了,則hash值加一,尋找7的位置,7的位置也被佔用了,回到0的位置。直到能夠插入爲止,能夠將這個數組當作一個環形數組。