深入探討java.lang.ThreadLocal類
一摊求、概述
ThreadLocal是什么呢校辩?其實ThreadLocal并非是一個線程的本地實現(xiàn)版本斋陪,它并不是一個Thread癌椿,而是一個ThreadLocalVariable(線程局部變量)躺坟。
變量值的共享可以使用
public static
變量的形式属桦,所有的線程都使用同一個public static
變量. 如果想實現(xiàn)每一個每一個線程都有自己的共享變量該如何解決呢? JDK中提供的類ThreadLocal正是為了解決這樣的問題.
類ThreadLocal主要解決的就是每個線程綁定自己的值,可以將 ThreadLocal類比喻成全局存放數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù).
從線程角度看熊痴,只要線程是活動的并且 ThreadLocal實例時可訪問的,那么每個線程都保持一個對其線程共享的私有變量副本的隱式引用聂宾,在線程消失之后果善,其線程的所有私有變量副本都會被垃圾回收(除非存在對這些變量副本的其他引用)。
通過ThreadLocal存取的數(shù)據(jù)系谐,總是與當(dāng)前線程相關(guān)巾陕,也就是說,JVM為每個運行的線程,綁定了私有的本地實例存取空間惜论,從而為多線程環(huán)境常出現(xiàn)的并發(fā)訪問問題提供了一種隔離機制许赃。
ThreadLocal 是如何做到為每一個線程維護私有變量副本的呢?其實實現(xiàn)的思路很簡單馆类,在以前混聊,底層實現(xiàn)是一個HashMap,key是當(dāng)前線程乾巧,value是該實例句喜。但是現(xiàn)在的設(shè)計思路改了!沟于!現(xiàn)在的底層實現(xiàn)是Thread個HashMap咳胃,每個HashMap的key是這個ThreadLocal實例,value是那個對象的副本旷太。
ThreadLocal在1.6版本后是在Thread類中有一個ThreadLocalMap的變量展懈,然后用Thread.currentThread().threadLocals.get(this)來引用的各線程變量副本.
為什么這樣搞呢?如果是原來的設(shè)計方案供璧,那么在大型項目里有很多Thread和很多ThreadLocal的前提下存崖,就會有ThreadLocal個HashMap,每個里面就有Thread個元素睡毒。在Thread很多的情況下性能會低来惧。
還有一點,當(dāng)一個線程停止時演顾,對應(yīng)的ThreadLocal副本都不存在了供搀,可以銷毀一個HashMap。但用第一種設(shè)計思路的話這些HashMap都在钠至。
概括起來說葛虐,對于多線程資源共享問題,同步機制采用了“以時間換空間”的方式棉钧,而ThreadLocal采用了“以空間換時間”的方式挡闰。前者僅提供一份變量,在不同線程排隊訪問掰盘,而后者為每一個線程都提供了一份變量,因此可以同時訪問而不互相影響赞季。
二愧捕、API說明
ThreadLocal() -->創(chuàng)建一個線程本地變量
get() -->返回線程本地變量的當(dāng)前線程副本中的值,如果第一次調(diào)用get()
方法則返回的值是null
protected T initialValue() --> 返回此線程本地變量的當(dāng)前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調(diào)用此方法一次申钩,即線程第一次使用 get() 方法訪問變量的時候次绘。如果線程先于 get 方法調(diào)用 set(T) 方法,則不會在線程中再調(diào)用 initialValue 方法
set(T value) -->將線程本地變量的當(dāng)前線程副本中的值設(shè)置為指定值.許多應(yīng)用程序不需要此方法,它們只依賴于initialValue()方法來設(shè)置線程局部變量的值.
void remove() --> 移除此線程局部變量的值,這可能有助于減少線程局部變量的存儲需求邮偎。如果再次訪問此線程局部變量管跺,那么在默認情況下它將擁有其 initialValue。
在程序中一般都重寫initialValue方法禾进,以給定一個特定的初始值豁跑。
三、代碼實例
3.1 方法get()與null
public class ThreadLocalTest {
public static ThreadLocal t1 = new ThreadLocal();
public static void main(String[] args) {
if(t1.get() == null) {
System.out.println("從未放過值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
運行結(jié)果:
從未放過值
我的值
我的值
從上面的運行結(jié)果來看,第一次調(diào)用t1對象的get()
方法時返回的值是null,通過調(diào)用set()
方法賦值后順利取出值并打印到控制臺上.說明不同線程中的值是可以放入ThreadLocal類中進行保存的;
3.2 Hibernate的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創(chuàng)建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失斝涸啤艇拍!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//創(chuàng)建線程局部變量session,用來保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
/**
* 獲取當(dāng)前線程中的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 {
//獲取線程局部變量卸夕,并強制轉(zhuǎn)換為Session類型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
在這個類中,由于沒有重寫ThreadLocal的initialValue()方法婆瓜,則首次創(chuàng)建線程局部變量session其初始值為null快集,第一次調(diào)用currentSession()的時候,線程局部變量的get()方法也為null廉白。因此个初,對session做了判斷,如果為null蒙秒,則新開一個Session勃黍,并保存到線程局部變量session中,這一步非常的關(guān)鍵晕讲,這也是public static final ThreadLocal session = new ThreadLocal()
所創(chuàng)建的對象session通過get()
獲取的對象能強制轉(zhuǎn)換為Hibernate Session對象的原因覆获。
3.3 驗證線程變量的隔離性
public class Tools {
public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
@Override
public void run() {
try{
for (int i = 0; i < 20; i++) {
Tools.t1.set("ThreadA" + (i+1));
Thread.sleep(200);
System.out.println("ThreadA get Value = " +Tools.t1.get());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
@Override
public void run() {
try{
for (int i = 0; i < 20; i++) {
Tools.t1.set("ThreadB" + (i+1));
Thread.sleep(200);
System.out.println("ThreadB get Value = " + Tools.t1.get());
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Run {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b= new ThreadB();
a.start();
b.start();
for (int i = 0; i < 20; i++) {
Tools.t1.set("Main" + (i+1));
Thread.sleep(200);
System.out.println("Main get Value = " + Tools.t1.get());
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
雖然三個線程都向t1對象中set()
數(shù)據(jù)值,但每個線程還是能取出自己的數(shù)據(jù)。
3.4 解決get() 返回null問題
public class ThreadLocalExt extends ThreadLocal{
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
* <p>
* <p>This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
@Override
protected Object initialValue() {
return "我是默認值 第一次get不再為null";
}
}
class run{
public static ThreadLocalExt t1 = new ThreadLocalExt();
public static void main(String[] args) {
if(t1.get() == null) {
System.out.println("從未放過值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
運行結(jié)果:
我是默認值 第一次get不再為null
我是默認值 第一次get不再為null
此案例僅僅證明main線程有自己的值,那其他線程是否會有自己的初始值呢?
3.5 再次驗證線程變量的隔離性
public class Tools {
public static ThreadLocalExt t1 = new ThreadLocalExt();
}
class ThreadLocalExt extends ThreadLocal {
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
* <p>
* <p>This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
@Override
protected Object initialValue() {
return new Date().getTime();
}
}
class ThreadA extends Thread {
@Override
public void run() {
try{
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA 線程中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Run {
public static void main(String[] args) {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在Main線程中取值 = " + Tools.t1.get());
Thread.sleep(100);
}
Thread.sleep(5000);
ThreadA a = new ThreadA();
a.start();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運行結(jié)果:
子線程和父線程各有各自所擁有的值;