天天看點

ThreadLocal使用場景分析

ThreadLocal<T>其實是與線程綁定的一個變量。ThreadLocal和Synchonized都用于解決多線程并發通路。但是ThreadLocal與synchronized有本質的差別。Synchronized用于線程間的資料共享,而ThreadLocal則用于線程間的資料隔離。Synchronized是利用鎖的機制,使變量或代碼塊在某一時該隻能被一個線程通路。而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間通路到的并不是同一個對象,這樣就隔離了多個線程對資料的資料共享。而Synchronized卻正好相反,它用于在多個線程間通信時能夠獲得資料共享。

一句話了解ThreadLocal,向ThreadLocal裡面存東西就是向它裡面的Map存東西的,然後ThreadLocal把這個Map挂到目前的線程底下,這樣Map就隻屬于這個線程了。

ThreadLocal源碼

ThreadLocal其實是與線程綁定的一個變量,如此就會出現一個問題:如果沒有将ThreadLocal内的變量删除(remove)或替換,它的生命周期将會與線程共存。通常線程池中對線程管理都是采用線程複用的方法,線上程池中線程很難結束甚至于永遠不會結束,這将意味着線程持續的時間将不可預測,甚至與JVM的生命周期一緻。舉個例字,如果ThreadLocal中直接或間接包裝了集合類或複雜對象,每次在同一個ThreadLocal中取出對象後,再對内容做操作,那麼内部的集合類和複雜對象所占用的空間可能會開始持續膨脹。

How to use ThreadLocal

Spring使用ThreadLocal解決線程安全問題。通常隻有無狀态的Bean才可以在多線程環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全的“狀态性對象”采用ThreadLocal進行封裝,讓它們也成為線程安全的“狀态性對象”,是以有狀态的Bean就能夠以singleton的方式在多線程中正常工作了。一般的Web應用劃分為控制層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過接口向上層開放功能調用。在一般情況下,從接收請求到傳回響應所經過的所有程式調用都同屬于一個線程。這樣使用者就可以根據需要,将一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,所有對象所通路的同一ThreadLocal變量都是目前線程所綁定的。

由于①處的conn是成員變量,因為addTopic()方法是非線程安全的,必須在使用時建立一個新TopicDao執行個體(非singleton)。下面使用ThreadLocal對conn這個非線程安全的“狀态”進行改造:

不同的線程在使用TopicDao時,先判斷connThreadLocal.get()是否為null,如果為null,則說明目前線程還沒有對應的Connection對象,這時建立一個Connection對象并添加到本地線程變量中;如果不為null,則說明目前的線程已經擁有了Connection對象,直接使用就可以了。這樣,就保證了不同的線程使用線程相關的Connection,而不會使用其他線程的Connection。是以,這個TopicDao就可以做到singleton共享了。 當然,這個例子本身很粗糙,将Connection的ThreadLocal直接放在Dao隻能做到本Dao的多個方法共享Connection時不發生線程安全問題,但無法和其他Dao共用同一個Connection,要做到同一事務多Dao共享同一個Connection,必須在一個共同的外部類使用ThreadLocal儲存Connection。但這個執行個體基本上說明了Spring對有狀态類線程安全化的解決思路。