深刻研究java.lang.ThreadLocal類
1、概述
ThreadLocal是什麼呢?其實ThreadLocal並不是是一個線程的本地實現版本,它並非一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名爲ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用很是簡單,就是爲每個使用該變量的線程都提供一個變量值的副本,是Java中一種較爲特殊的線程綁定機制,是每個線程均可以獨立地改變本身的副本,而不會和其它線程的副本衝突。
從線程的角度看,每一個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的而且 ThreadLocal 實例是可訪問的;在線程消失以後,其線程局部實例的全部副本都會被垃圾回收(除非存在對這些副本的其餘引用)。
經過ThreadLocal存取的數據,老是與當前線程相關,也就是說,JVM 爲每一個運行的線程,綁定了私有的本地實例存取空間,從而爲多線程環境常出現的併發訪問問題提供了一種隔離機制。
ThreadLocal是如何作到爲每個線程維護變量的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於存儲每個線程的變量的副本。
歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。
2、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方法,以給定一個特定的初始值。
3、典型實例
一、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對象的緣由。
二、另一個實例
建立一個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 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,既實現多線程併發,遊兼顧數據的安全性。
4、總結
ThreadLocal使用場合主要解決多線程中數據數據因併發產生不一致問題。ThreadLocal爲每一個線程的中併發訪問的數據提供一個副本,經過訪問副原本運行業務,這樣的結果是耗費了內存,單大大減小了線程同步所帶來性能消耗,也減小了線程併發控制的複雜度。
ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。
ThreadLocal和Synchonized都用於解決多線程併發訪問。可是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變量或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal爲每個線程都提供了變量的副本,使得每一個線程在某一時間訪問到的並非同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用於在多個線程間通訊時可以得到數據共享。
Synchronized用於線程間的數據共享,而ThreadLocal則用於線程間的數據隔離。
固然ThreadLocal並不能替代synchronized,它們處理不一樣的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。
5、ThreadLocal使用的通常步驟
一、在多線程的類(如ThreadDemo類)中,建立一個ThreadLocal對象threadXxx,用來保存線程間須要隔離處理的對象xxx。
二、在ThreadDemo類中,建立一個獲取要隔離訪問的數據的方法getXxx(),在方法中判斷,若ThreadLocal對象爲null時候,應該new()一個隔離訪問類型的對象,並強制轉換爲要應用的類型。
三、在ThreadDemo類的run()方法中,經過getXxx()方法獲取要操做的數據,這樣能夠保證每一個線程對應一個數據對象,在任什麼時候刻都操做的是這個對象。
參考文檔:
JDK 官方文檔
本文出自 「熔 巖」 博客,轉載請與做者聯繫! html