深入解析ThreadLocal底層實現原理
學習Java中常用的開源框架,Mybatis、Hibernate中設計到執行緒通過資料庫連線物件Connection,對其資料進行操作,都會使用ThreadLocal類來保證Java多執行緒程式訪問和資料庫資料的一致性問題。就想深入瞭解一下ThreadLocal類是怎樣確保執行緒安全的!詳解如下:
一、對其ThreadLocal類的大致瞭解
ThreadLocal ,也叫執行緒本地變數,可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了所使用的的變數副本。使用起來都是線上程的本地工作記憶體中操作,並且提供了set和get方法來訪問拷貝過來的變數副本。底層也是封裝了ThreadLocalMap集合類來綁定當前執行緒和變數副本的關係,各個執行緒獨立並且訪問安全!
- publicclass DBUtil {
- //建立一個儲存資料庫連線物件的ThreadLocal執行緒本地變數
- privatestatic ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
- static{
- try {
- //註冊驅動
- DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- /*
- * 獲取資料庫的連線物件
- */
- publicstatic Connection getConnected(){
- Connection conn = null;
- conn = tl.get(); //第一步:從ThreadLocal物件當中去獲取
- if(conn == null){ //若沒有獲取到,原始方法獲取
- try {
- conn = DriverManager.getConnection("jdbc:oracle:thin:@192.168.122.1:1521/xe"
- //獲取連線物件以後,都設定為預設手動提交
- conn.setAutoCommit(false);
- //第二部:將連線物件放入對應的ThreadLocal泛型物件tl當中(進而繫結到使用它的執行緒物件上)
- tl.set(conn);
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- return conn;
- }
- /*
- * 關閉資料庫的連線,並刪除對應的ThreadLocal中的物件
- */
- publicstaticvoid closeConnection(){
- Connection conn = null;
- conn = tl.get(); //第三步:使用完畢,再次獲取物件
- if(conn != null){
- tl.remove(); //第四步:執行緒操作資料庫完畢,移除
- try {
- conn.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- }
- }
上述例子中使用ThreadLocal類來繫結對應執行緒和Connection之間的關係,確保訪問資料庫資料的安全性問題;大家想象一下,如果沒有使用ThreadLocal類來繫結,那麼多個執行緒同時進入getConnected()方法,有可能獲取的是同一個Connection物件,導致執行緒不安全問題!
二、深入理解ThreadLoca類
(1)set操作,為執行緒繫結變數:
- publicvoid set(T value) {
- Thread t = Thread.currentThread();//1.首先獲取當前執行緒物件
- ThreadLocalMap map = getMap(t);//2.獲取該執行緒物件的ThreadLocalMap
- if (map != null)
- map.set(this, value);//如果map不為空,執行set操作,以當前threadLocal物件為key,實際儲存物件為value進行set操作
- else
- createMap(t, value);//如果map為空,則為該執行緒建立ThreadLocalMap
- }
可以很清楚的看到,ThreadLocal只不過是個入口,真正的變數副本繫結到當前執行緒上的。
- //Thread中的成員變數
- ThreadLocal.ThreadLocalMap threadLocals = null; //每個Thread執行緒中都封裝了一個ThreadLocalMap物件
- //ThreadLocal類中獲取Thread類中的ThreadLocalMap物件
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
- //ThreadLocal類中建立Thread類中的ThreadLocalMap成員物件
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
現在,我們可以看出ThreadLocal的設計思想了:
(1) ThreadLocal僅僅是個變數訪問的入口;
(2) 每一個Thread物件都有一個ThreadLocalMap物件,這個ThreadLocalMap持有物件的引用;
(3) ThreadLocalMap以當前的threadLocal物件為key,以真正的儲存物件為value。get()方法時通過threadLocal例項就可以找到繫結在當前執行緒上的副本物件。
看上去有點繞。我們完全可以設計成Map<Thread,Value>這種形式,一個執行緒對應一個儲存物件。
ThreadLocal這樣設計有兩個目的:
第一:可以保證當前執行緒結束時,相關物件可以立即被回收;第二:ThreadLocalMap元素會大大減少,因為Map過大容易造成雜湊衝突而導致效能降低。
(2)看get()方法
- public T get() {
- Thread t = Thread.currentThread();//1.首先獲取當前執行緒
- ThreadLocalMap map = getMap(t);//2.獲取執行緒的map物件
- if (map != null) {//3.如果map不為空,以threadlocal例項為key獲取到對應Entry,然後從Entry中取出物件即可。
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null)
- return (T)e.value;
- }
- return setInitialValue();//如果map為空,也就是第一次沒有呼叫set直接get(或者呼叫過set,又呼叫了remove)時,為其設定初始值
- }
- void createMap(Thread t, T firstValue) { //this指的是ThreadLocal物件
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
- private T setInitialValue() {
- T value = initialValue();
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- return value;
- }
三、應用場景:
ThreadLocal物件通常用於房子對可變的單例項變數或全域性變數進行共享。例如:由於JDBC的連線物件不是執行緒安全的,因此,當多個執行緒應用程式在沒有協同的情況下,使用全域性變數時,就是執行緒不安全的。通過將JDBC的連線物件儲存到ThreadLocal中,每個執行緒都會擁有自己的連線物件副本。
ThreadLocal在Spring的事物管理,包括Hibernate管理等都有出現,在web開發中,有事會用來管理使用者回話HttpSession,web互動這種典型的一請求一執行緒的場景似乎比較適合使用ThreadLocal,但是需要注意的是,由於此時session與執行緒關聯,而Tomcat這些web伺服器多采用執行緒池機制,也就是說執行緒是可以複用的,所以在每次進入的時候都需要重新進行set操作,或者使用完畢以後及時remove掉!
- publicvoid remove() {
- ThreadLocalMap m = getMap(Thread.currentThread()); //先獲取ThreadLocalMap物件例項
- if (m != null) //直接通過threadLocal例項刪除value值
- m.remove(this);
- }