一、概述
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方法,以給定一個特定的初始值。
三、典型實例
1、Hiberante的Session 工具類別HibernateUtil
# 這個類別是Hibernate官方文件中HibernateUtil類,用於session管理。
public class HibernateUtil {
# private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定義SessionFactory
# static {
try {
// 透過預設設定檔hibernate.cfg.xml建立SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
# log.error("初始化SessionFactory失敗!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//建立執行緒局部變數session,用來儲存Hibernate的Session
# public static final ThreadLocal session = new ThreadLocal();
/**
* 取得目前執行緒中的Session
# * @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
# // 如果Session還沒打開,則新開一個Session
if (s == null) {
# s = sessionFactory.openSession();
session.set(s); //將新開的Session儲存到執行緒局部變數中
}
return s;
}
public static void closeSession() throws HibernateException {
# //取得線程局部變量,並強制轉換為Session類型
Session s = (Session) session.get();
# session.set(null);
# if (s != null)
# s.close();
}
}
在這個類別中,由於沒有重寫ThreadLocal的initialValue()方法,則首次建立線程局部變數session其初始值為null,第一次呼叫currentSession()的時候,線程局部變數的get()方法也為null 。因此,對session做了判斷,如果為null,則新開一個Session,並保存到線程局部變量session中,這一步非常的關鍵,這也是“public static final ThreadLocal session = new ThreadLocal()”所創建對象session能強制轉換為Hibernate Session物件的原因。
2、另外一個實例
建立一個Bean,透過不同的執行緒物件設定Bean屬性,確保各個執行緒Bean物件的獨立性。
/**
* 由 IntelliJ IDEA 建立。
# * 使用者:雷志敏
* 日期: 2007-11-23
* 時間:10:45:02
* 學生
*/
public class Student {
private int age = 0; //年齡
public int getAge() {
# return this.age;
}
public void setAge(int age) {
this.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");
# t1.start();
t2.start();
}
public void run() {
# accessStudent();
# }
/**
* 範例業務方法,用來測試
#*/
public void accessStudent() {
//取得目前執行緒的名字
String currentThreadName = Thread.currentThread().getName();
# System.out.println(currentThreadName " is running!");
//產生一個隨機數並列印
Random random = new Random();
int age = random.nextInt(100);
# System.out.println("thread " currentThreadName " set age to:" age);
//取得一個Student對象,並將隨機數年齡插入到對象屬性中
# Student student = getStudent();
student.setAge(age);
# System.out.println("thread " currentThreadName " first read age is:" student.getAge());
try {
Thread.sleep(500);
# }
catch (InterruptedException ex) {
ex.printStackTrace();
# }
System.out.println("thread " currentThreadName " second read age is:" student.getAge());
}
protected Student getStudent() {
//取得本機執行緒變數並強制轉換為Student類型
Student student = (Student) studentLocal.get();
//執行緒首次執行此方法的時候,studentLocal.get()肯定為null
if (student == null) {
//建立一個Student對象,並儲存到本機執行緒變數studentLocal中
student = new Student();
# studentLocal.set(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使用場合主要解決多執行緒中資料資料因並發產生不一致問題。 ThreadLocal為每個線程的中並發訪問的數據提供一個副本,通過訪問副本來運行業務,這樣的結果是耗費了內存,單大大減少了線程同步所帶來性能消耗,也減少了線程並發控制的複雜度。
ThreadLocal不能使用原子型,只能使用Object型別。 ThreadLocal的使用比synchronized簡單得多。
ThreadLocal和Synchonized都用於解決多執行緒並發存取。但是ThreadLocal與synchronized有本質上的差異。 synchronized是利用鎖的機制,使變數或程式碼區塊在某一時該只能被一個執行緒存取。而ThreadLocal為每個線程都提供了變數的副本,使得每個線程在某一時間訪問到的並不是同一個對象,這樣就隔離了多個線程對資料的資料共享。而Synchronized卻正好相反,它用於在多個執行緒間通訊時能夠獲得資料共享。
Synchronized用於線程間的資料共享,而ThreadLocal則用於線程間的資料隔離。
當然ThreadLocal並不能取代synchronized,它們處理不同的問題域。 Synchronized用於實現同步機制,比ThreadLocal更加複雜。
五、ThreadLocal所使用的一般步驟
# 1.在多執行緒的類別(如ThreadDemo類別)中,建立一個ThreadLocal物件threadXxx,用來保存線程間需要隔離處理的物件xxx。
2.在ThreadDemo類別中,建立一個取得要隔離存取的資料的方法getXxx(),在方法中判斷,若ThreadLocal物件為null時候,應該new()一個隔離存取類型的對象,並強制轉換為要套用的類型。
3.在ThreadDemo類別的run()方法中,透過getXxx()方法取得要操作的數據,這樣可以保證每個執行緒對應一個資料對象,在任何時刻都操作的是這個對象。
以上是java.lang.ThreadLocal類別怎麼用的詳細內容。更多資訊請關注PHP中文網其他相關文章!