一、概述
ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線程的本地實(shí)現(xiàn)版本,它并不是一個(gè)Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為T(mén)hreadLocalVar更加合適。線程局部變量(ThreadLocal)其實(shí)的功用非常簡(jiǎn)單,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是Java中一種較為特殊的線程綁定機(jī)制,是每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線程的副本沖突。
從線程的角度看,每個(gè)線程都保持一個(gè)對(duì)其線程局部變量副本的隱式引用,只要線程是活動(dòng)的并且 ThreadLocal 實(shí)例是可訪問(wèn)的;在線程消失之后,其線程局部實(shí)例的所有副本都會(huì)被垃圾回收(除非存在對(duì)這些副本的其他引用)。
通過(guò)ThreadLocal存取的數(shù)據(jù),總是與當(dāng)前線程相關(guān),也就是說(shuō),JVM 為每個(gè)運(yùn)行的線程,綁定了私有的本地實(shí)例存取空間,從而為多線程環(huán)境常出現(xiàn)的并發(fā)訪問(wèn)問(wèn)題提供了一種隔離機(jī)制。
ThreadLocal是如何做到為每一個(gè)線程維護(hù)變量的副本的呢?其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單,在ThreadLocal類中有一個(gè)Map,用于存儲(chǔ)每一個(gè)線程的變量的副本。
概括起來(lái)說(shuō),對(duì)于多線程資源共享的問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,讓不同的線程排隊(duì)訪問(wèn),而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而互不影響。
二、API說(shuō)明
ThreadLocal()
創(chuàng)建一個(gè)線程本地變量。
T get()
返回此線程局部變量的當(dāng)前線程副本中的值,如果這是線程第一次調(diào)用該方法,則創(chuàng)建并初始化此副本。
protected T initialValue()
返回此線程局部變量的當(dāng)前線程的初始值。最多在每次訪問(wèn)線程來(lái)獲得每個(gè)線程局部變量時(shí)調(diào)用此方法一次,即線程第一次使用 get() 方法訪問(wèn)變量的時(shí)候。如果線程先于 get 方法調(diào)用 set(T) 方法,則不會(huì)在線程中再調(diào)用 initialValue 方法。
若該實(shí)現(xiàn)只返回 null;如果程序員希望將線程局部變量初始化為 null 以外的某個(gè)值,則必須為 ThreadLocal 創(chuàng)建子類,并重寫(xiě)此方法。通常,將使用匿名內(nèi)部類。initialValue 的典型實(shí)現(xiàn)將調(diào)用一個(gè)適當(dāng)?shù)臉?gòu)造方法,并返回新構(gòu)造的對(duì)象。
void remove()
移除此線程局部變量的值。這可能有助于減少線程局部變量的存儲(chǔ)需求。如果再次訪問(wèn)此線程局部變量,那么在默認(rèn)情況下它將擁有其 initialValue。
void set(T value)
將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值。許多應(yīng)用程序不需要這項(xiàng)功能,它們只依賴于 initialValue() 方法來(lái)設(shè)置線程局部變量的值。
在程序中一般都重寫(xiě)initialValue方法,以給定一個(gè)特定的初始值。
三、典型實(shí)例
1、Hiberante的Session 工具類HibernateUtil
這個(gè)類是Hibernate官方文檔中HibernateUtil類,用于session管理。
public class HibernateUtil {
private static Log log = LogFactory.getLog(HibernateUtil.class);
private static final SessionFactory sessionFactory; //定義SessionFactory
static {
try {
// 通過(guò)默認(rèn)配置文件hibernate.cfg.xml創(chuàng)建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失??!", ex);
throw new ExceptionInInitializerError(ex);
}
}
通常在多線程中,當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本
實(shí)現(xiàn)線程本地類其實(shí)不難:以當(dāng)前線程為key,要保存的對(duì)象為value
public class ThreadLocalSample {
private Map map = Collections.synchronizedMap(new HashMap());
public void set(Object value) {
map.put(Thread.currentThread(), value);
}
public Object get() {
Object value = map.get(Thread.currentThread());
return value;
}
}
ThreadLocal可用在Servlet的過(guò)濾器中,比如:每一個(gè)請(qǐng)求都由一個(gè)Connection來(lái)操作數(shù)據(jù)庫(kù),由于Servlet只有一個(gè)實(shí)例,所以應(yīng)該是每個(gè)線程用一個(gè)連接,而不是所有線程用一個(gè)Connection,因此需要用到ThreadLocal類;由于Servlet是在Filter鏈的調(diào)用中點(diǎn)執(zhí)行,即Filter1->Filter2->...->Servlet->Filter2->Filter1,因此:可以用一個(gè)Filter,在這個(gè)Filter調(diào)用下一個(gè)Filter前初始化連接,并將其放入ThreadLocal中,在調(diào)用又回到這個(gè)Filter時(shí),得到連接并關(guān)閉。
代碼如下:
public class TransactionManageFilter implements Filter {
private FilterConfig config;
public void init(FilterConfig config) throws ServletException {
this.config = config;
}
public void destroy() {
config = null;
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
Connection conn = ConnectionManager.currentConnection();
try {
conn.setAutoCommit(false);
chain.doFilter(request, response, chain);
conn.commit();
} catch (Exception e) {
conn.rollback();
} finally {
try {
conn.setAutoCommit(true);
conn.close();
ConnectionManager.removeConnection();
} catch (Exception e) {}
}
}
}
public class ConnectionManager {
private static ThreadLocal currConn = new ThreadLocal();
public static Connection currentConnection() {
Object obj = currConn.get();
if (obj != null) {
return (Connection)obj;
} else {
Connection conn = ConnectionFactory.getConnection();
currConn.set(conn);
return conn;
}
}
public static void removeConnection() {
Object obj = currConn.get();
if (obj != null) {
currConn.set(null);
}
}
}
聯(lián)系客服