理解ThreadLocal(執行緒區域性變數)
阿新 • • 發佈:2019-02-05
ThreadLocal(執行緒區域性變數)概述
ThreadLocal是什麼呢?其實ThreadLocal並非是一個執行緒的本地實現版本,它並不是一個Thread,而是threadlocalvariable(執行緒區域性變數)。也許把它命名為ThreadLocalVar更加合適。執行緒區域性變數(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變數的執行緒都提供一個變數值的副本,是Java中一種較為特殊的執行緒繫結機制,是每一個執行緒都可以獨立地改變自己的副本,而不會和其它執行緒的副本衝突。 從執行緒的角度看,每個執行緒都保持一個對其執行緒區域性變數副本的隱式引用,只要執行緒是活動的並且 ThreadLocal 例項是可訪問的;線上程消失之後,其執行緒區域性例項的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。 通過ThreadLocal存取的資料,總是與當前執行緒相關,也就是說,JVM 為每個執行的執行緒,綁定了私有的本地例項存取空間,從而為多執行緒環境常出現的併發訪問問題提供了一種隔離機制。 ThreadLocal是如何做到為每一個執行緒維護變數的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於儲存每一個執行緒的變數的副本。 概括起來說,對於多執行緒資源共享的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的執行緒排隊訪問,而後者為每一個執行緒都提供了一份變數,因此可以同時訪問而互不影響。API:
經典案例:
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物件的獨立性。
/**
* 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 總結
ThreadLocal使用場合主要解決多執行緒中資料資料因併發產生不一致問題。ThreadLocal為每個執行緒的中併發訪問的資料提供一個副本,通過訪問副本來執行業務,這樣的結果是耗費了記憶體,單大大減少了執行緒同步所帶來效能消耗,也減少了執行緒併發控制的複雜度。 ThreadLocal不能使用原子型別,只能使用Object型別。ThreadLocal的使用比synchronized要簡單得多。 ThreadLocal和Synchonized都用於解決多執行緒併發訪問。但是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變數或程式碼塊在某一時該只能被一個執行緒訪問。而ThreadLocal為每一個執行緒都提供了變數的副本,使得每個執行緒在某一時間訪問到的並不是同一個物件,這樣就隔離了多個執行緒對資料的資料共享。而Synchronized卻正好相反,它用於在多個執行緒間通訊時能夠獲得資料共享。 Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。 當然ThreadLocal並不能替代synchronized,它們處理不同的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。 ThreadLocal使用的一般步驟