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方法,以給定一個特定的初始值。
public class ThreadLocal
{
private Map values = Collections.synchronizedMap(new HashMap());
public Object get()
{
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread))
{
o = initialValue();
values.put(curThread, o);
}
return o;
}
public void set(Object newValue)
{
values.put(Thread.currentThread(), newValue);
}
public Object initialValue()
{
return null;
}
}
三 典型實例
Hiberante的Session工具類HibernateUtil。這個類是Hibernate官方文檔中HibernateUtil類,用於session管理。
public class HibernateUtil
{
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final 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
public static Session currentSession() throws HibernateException
{
Session s = (Session) session.get();
if (s == null)
{
// 將新開的Session保存到線程局部變量中
s = sessionFactory.openSession();
session.set(s);
}
return s;
}
public static void closeSession() throws HibernateException
{
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
對代碼進行分析
1 public static final ThreadLocal session = new ThreadLocal();
2 public static Session currentSession() {
3 Session s = (Session)session.get();
4 if(s == null){
5 s = sessionFactory.openSession();
6 session.set(s);
7 }
8 return s;
9 }
總體分析
該類由於沒有重寫ThreadLocal的initialValue()方法,則首次創建線程局部變量session其初始值為null。第一次調用currentSession()的時候,線程局部變量的get()方法也為null。因此對session做了判斷如果為null,則新開一個Session,並保存到線程局部變量session中,這一步非常的關鍵。
逐行分析
第一行
初始化。如果不初始化initialvalue則initialvalue為null。
第三行
session的get根據當前線程返回其對應的線程內部變量,也就是我們需要的net.sf.hibernate.Session相當於對應每個數據庫連接。多線程情況下共享數據庫鏈接是不安全的。ThreadLocal保證了每個線程都有自己的s數據庫連接。
第四行
如果是該線程初次訪問,自然s會是null,接著創建一個Session,具體就是第五行。
第五行
創建一個數據庫連接實例s。
第六行
保存該數據庫連接s到ThreadLocal中。
第七行
如果當前線程已經訪問過數據庫了,則從session中get()就可以獲取該線程上次獲取過的連接實例。
四 總結
ThreadLocal使用場合主要解決多線程中數據因並發產生不一致問題。ThreadLocal為每個線程的中並發訪問的數據提供一個副本。
原帖地址:https://lavasoft.blog.51cto.com/62575/51926/
最後更新:2017-04-03 20:19:35