簡單地分析Java線程編程中ThreadLocal類的使用
一、概述
ThreadLocal是什麼呢?其實ThreadLocal並非是一個線程的本地實現版本,它並不是一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是Java中一種較為特殊的線程綁定機制,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本衝突。
從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的並且 ThreadLocal 實例是可訪問的;在線程消失之後,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。
通過ThreadLocal存取的數據,總是與當前線程相關,也就是説,JVM 為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環境常出現的併發訪問問題提供了一種隔離機制。
ThreadLocal是如何做到為每一個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於存儲每一個線程的變量的副本。
概括起來説,對於多線程資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的`方式。前者僅提供一份變量,讓不同的線程排隊訪問,而後者為每一個線程都提供了一份變量,因此可以同時訪問而互不影響。
二、API説明
ThreadLocal()
創建一個線程本地變量。
T get()
返回此線程局部變量的當前線程副本中的值,如果這是線程第一次調用該方法,則創建並初始化此副本。
protected T initialValue()
返回此線程局部變量的當前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。如果線程先於 get 方法調用 set(T) 方法,則不會在線程中再調用 initialValue 方法。
若該實現只返回 null;如果程序員希望將線程局部變量初始化為 null 以外的某個值,則必須為 ThreadLocal 創建子類,並重寫此方法。通常,將使用匿名內部類。initialValue 的典型實現將調用一個適當的構造方法,並返回新構造的對象。
void remove()
移除此線程局部變量的值。這可能有助於減少線程局部變量的存儲需求。如果再次訪問此線程局部變量,那麼在默認情況下它將擁有其 initialValue。
void set(T value)
將此線程局部變量的當前線程副本中的值設置為指定值。許多應用程序不需要這項功能,它們只依賴於 initialValue() 方法來設置線程局部變量的值。
在程序中一般都重寫initialValue方法,以給定一個特定的初始值。
三、一.對ThreadLocal的理解
ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲,其實意思差不多。可能很多朋友都知道ThreadLocal為變量在每個線程中都創建了一個副本,那麼每個線程可以訪問自己內部的副本變量。
這句話從字面上看起來很容易理解,但是真正理解並不是那麼容易。
我們還是先來看一個例子:
class ConnectionManager { private static Connection connect = null; public static Connection openConnection() { if(connect == null){ connect = onnection(); } return connect; } public static void closeConnection() { if(connect!=null) e(); }}
假設有這樣一個數據庫鏈接管理類,這段代碼在單線程中使用是沒有任何問題的,但是如果在多線程中使用呢?很顯然,在多線程中使用會存在線程安全問題:第一,這裏面的2個方法都沒有進行同步,很可能在openConnection方法中會多次創建connect;第二,由於connect是共享變量,那麼必然在調用connect的地方需要使用到同步來保障線程安全,因為很可能一個線程在使用connect進行數據庫操作,而另外一個線程調用closeConnection關閉鏈接。
所以出於線程安全的考慮,必須將這段代碼的兩個方法進行同步處理,並且在調用connect的地方需要進行同步處理。
這樣將會大大影響程序執行效率,因為一個線程在使用connect進行數據庫操作的時候,其他線程只有等待。
那麼大家來仔細分析一下這個問題,這地方到底需不需要將connect變量進行共享?事實上,是不需要的。假如每個線程中都有一個connect變量,各個線程之間對connect變量的訪問實際上是沒有依賴關係的,即一個線程不需要關心其他線程是否對這個connect進行了修改的。
到這裏,可能會有朋友想到,既然不需要在線程之間共享這個變量,可以直接這樣處理,在每個需要使用數據庫連接的方法中具體使用時才創建數據庫鏈接,然後在方法調用完畢再釋放這個連接。比如下面這樣:
class ConnectionManager { private Connection connect = null; public Connection openConnection() { if(connect == null){ connect = onnection(); } return connect; } public void closeConnection() { if(connect!=null) e(); }} class Dao{ public void () { ConnectionManager connectionManager = new ConnectionManager(); Connection connection = Connection(); //使用connection進行操作 eConnection(); }}
這樣處理確實也沒有任何問題,由於每次都是在方法內部創建的連接,那麼線程之間自然不存在線程安全問題。但是這樣會有一個致命的影響:導致服務器壓力非常大,並且嚴重影響程序執行性能。由於在方法中需要頻繁地開啟和關閉數據庫連接,這樣不盡嚴重影響程序執行效率,還可能導致服務器壓力巨大。
那麼這種情況下使用ThreadLocal是再適合不過的了,因為ThreadLocal在每個線程中對該變量會創建一個副本,即每個線程內部都會有一個該變量,且在線程內部任何地方都可以使用,線程之間互不影響,這樣一來就不存在線程安全問題,也不會嚴重影響程序執行性能。
但是要注意,雖然ThreadLocal能夠解決上面説的問題,但是由於在每個線程中都創建了副本,所以要考慮它對資源的消耗,比如內存的佔用會比不使用ThreadLocal要大。
四、實例
創建一個Bean,通過不同的線程對象設置Bean屬性,保證各個線程Bean對象的獨立性。
/** * Created by IntelliJ IDEA. * User: leizhimin * Date: 2007-11-23 * Time: 10:45:02 * 學生 */public class Student { private int age = 0; //年齡 public int getAge() { return ; } public void setAge(int age) { = age; }} /** * Created by IntelliJ IDEA. * User: leizhimin * Date: 2007-11-23 * Time: 10:53:33 * 多線程下測試程序 */public class ThreadLocalDemo implements Runnable { //創建線程局部變量studentLocal,在後面你會發現用來保存Student對象 private final static ThreadLocal studentLocal = new ThreadLocal(); public static void main(String[] agrs) { ThreadLocalDemo td = new ThreadLocalDemo(); Thread t1 = new Thread(td, "a"); Thread t2 = new Thread(td, "b"); t(); t(); } public void run() { accessStudent(); } /** * 示例業務方法,用來測試 */ public void accessStudent() { //獲取當前線程的名字 String currentThreadName = entThread()ame(); tln(currentThreadName + " is running!"); //產生一個隨機數並打印 Random random = new Random(); int age = Int(100); tln("thread " + currentThreadName + " set age to:" + age); //獲取一個Student對象,並將隨機數年齡插入到對象屬性中 Student student = getStudent(); ge(age); tln("thread " + currentThreadName + " first read age is:" + ge()); try { p(500); } catch (InterruptedException ex) { tStackTrace(); } tln("thread " + currentThreadName + " second read age is:" + ge()); } protected Student getStudent() { //獲取本地線程變量並強制轉換為Student類型 Student student = (Student) (); //線程首次執行此方法的時候,()肯定為null if (student == null) { //創建一個Student對象,並保存到本地線程變量studentLocal中 student = new Student(); (student); } return student; }}
運行結果:
a is running! thread a set age to:76 b is running! thread b set age to:27 thread a first read age is:76 thread b first read age is:27 thread a second read age is:76 thread b second read age is:27
可以看到a、b兩個線程age在不同時刻打印的值是完全相同的。這個程序通過妙用ThreadLocal,既實現多線程併發,遊兼顧數據的安全性。
五、ThreadLocal使用的一般步驟
1、在多線程的類(如ThreadDemo類)中,創建一個ThreadLocal對象threadXxx,用來保存線程間需要隔離處理的對象xxx。
2、在ThreadDemo類中,創建一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應該new()一個隔離訪問類型的對象,並強制轉換為要應用的類型。
3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數據,這樣可以保證每個線程對應一個數據對象,在任何時刻都操作的是這個對象。
-
新手學習Java最高效的10個計劃
對於新手來説學習Java如同堵天書,下面是本站小編收集的關於新手學習Java最高效的10個計劃,希望可以為您的學習帶來幫助!計劃一:瞭解未來技術發展方向兩耳不聞窗外事,在如今是不合適的,尤其是在瞬息萬變的互聯網時代,作為一個開發者技術跟不上可以去專研,思想要跟不上,...
-
講述java中enum的用法
本文實例講述了java中enum的用法。分享給大家供大家參考。具體分析如下:1.基本用法複製代碼代碼如下:enumDay{SUNDAY,MONDAY,TUESDAY,WENDSDAY,THURSDAY,FRIDAY,SATURDAY;}枚舉是常量,所以應該用大寫。2.枚舉是對象枚舉隱含地繼承了,所以它具有的.屬性和方法。遍...
-
java中String和StringBuffer的區別
java中String和StringBuffer的區別String這個類是Java中使用得最頻繁的類之一,以下就是小編精心推薦java中String和StringBuffer的區別,希望對大家有幫助!看到這個講解的不錯,所以轉一下在java中有3個類來負責字符的操作。acter是進行單個字符操作的,ng對一串字符...
-
Java技術怎麼學習
對於很多隻會C語言的初學者而言,面對java基礎語法學習,反而感覺很難,其實其中最大的問題不是語法難,而是一種編程思想的轉變。怎麼學習才是正確的呢?下面是相關的知識,歡迎閲讀。1.概述學過一段時間的同學一定會覺得Java學習最頭疼的不是語法結構的繁雜,而是Java本身...
相關文章
- Java ClassLoader原理詳細分析2016
- 如何理解Javascript的caller,callee,call,apply區別
- 如何理解Javascript的caller,callee,call,apply區別
- Javascript實例教程如何使用HoTMetal
- 如何理解Javascript的caller,callee,call,apply區別
- 如何理解Javascript的caller,callee,call,apply區別
- A Lifeguard at Johns Beach Saved a Drowning Child,A Lifeguar英語作文
- 如何理解Javascript的caller,callee,call,apply區別
- Java併發編程:深入剖析ThreadLocal
- 如何理解Javascript的caller,callee,call,apply區別