深入探討java.lang.ThreadLocal類
一、概述
ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線程的本地實(shí)現(xiàn)版本,它并不是一個(gè)Thread,而是一個(gè)ThreadLocalVariable(線程局部變量)。
變量值的共享可以使用
public static
變量的形式,所有的線程都使用同一個(gè)public static
變量. 如果想實(shí)現(xiàn)每一個(gè)每一個(gè)線程都有自己的共享變量該如何解決呢? JDK中提供的類ThreadLocal正是為了解決這樣的問(wèn)題.
類ThreadLocal主要解決的就是每個(gè)線程綁定自己的值,可以將 ThreadLocal類比喻成全局存放數(shù)據(jù)的盒子,盒子中可以存儲(chǔ)每個(gè)線程的私有數(shù)據(jù).
從線程角度看,只要線程是活動(dòng)的并且 ThreadLocal實(shí)例時(shí)可訪問(wèn)的,那么每個(gè)線程都保持一個(gè)對(duì)其線程共享的私有變量副本的隱式引用,在線程消失之后,其線程的所有私有變量副本都會(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)單,在以前,底層實(shí)現(xiàn)是一個(gè)HashMap,key是當(dāng)前線程,value是該實(shí)例。但是現(xiàn)在的設(shè)計(jì)思路改了!!現(xiàn)在的底層實(shí)現(xiàn)是Thread個(gè)HashMap,每個(gè)HashMap的key是這個(gè)ThreadLocal實(shí)例,value是那個(gè)對(duì)象的副本。
ThreadLocal在1.6版本后是在Thread類中有一個(gè)ThreadLocalMap的變量,然后用Thread.currentThread().threadLocals.get(this)來(lái)引用的各線程變量副本.
為什么這樣搞呢?如果是原來(lái)的設(shè)計(jì)方案,那么在大型項(xiàng)目里有很多Thread和很多ThreadLocal的前提下,就會(huì)有ThreadLocal個(gè)HashMap,每個(gè)里面就有Thread個(gè)元素。在Thread很多的情況下性能會(huì)低。
還有一點(diǎn),當(dāng)一個(gè)線程停止時(shí),對(duì)應(yīng)的ThreadLocal副本都不存在了,可以銷毀一個(gè)HashMap。但用第一種設(shè)計(jì)思路的話這些HashMap都在。
概括起來(lái)說(shuō),對(duì)于多線程資源共享問(wèn)題,同步機(jī)制采用了“以時(shí)間換空間”的方式,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量,在不同線程排隊(duì)訪問(wèn),而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而不互相影響。
二、API說(shuō)明
ThreadLocal() -->創(chuàng)建一個(gè)線程本地變量
get() -->返回線程本地變量的當(dāng)前線程副本中的值,如果第一次調(diào)用get()
方法則返回的值是null
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 方法
set(T value) -->將線程本地變量的當(dāng)前線程副本中的值設(shè)置為指定值.許多應(yīng)用程序不需要此方法,它們只依賴于initialValue()方法來(lái)設(shè)置線程局部變量的值.
void remove() --> 移除此線程局部變量的值,這可能有助于減少線程局部變量的存儲(chǔ)需求。如果再次訪問(wèn)此線程局部變量,那么在默認(rèn)情況下它將擁有其 initialValue。
在程序中一般都重寫initialValue方法,以給定一個(gè)特定的初始值。
三、代碼實(shí)例
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("從未放過(guò)值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
運(yùn)行結(jié)果:
從未放過(guò)值
我的值
我的值
從上面的運(yùn)行結(jié)果來(lái)看,第一次調(diào)用t1對(duì)象的get()
方法時(shí)返回的值是null,通過(guò)調(diào)用set()
方法賦值后順利取出值并打印到控制臺(tái)上.說(shuō)明不同線程中的值是可以放入ThreadLocal類中進(jìn)行保存的;
3.2 Hibernate的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);
}
}
//創(chuàng)建線程局部變量session,用來(lái)保存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還沒(méi)有打開(kāi),則新開(kāi)一個(gè)Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //將新開(kāi)的Session保存到線程局部變量中
}
return s;
}
public static void closeSession() throws HibernateException {
//獲取線程局部變量,并強(qiáng)制轉(zhuǎn)換為Session類型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
在這個(gè)類中,由于沒(méi)有重寫ThreadLocal的initialValue()方法,則首次創(chuàng)建線程局部變量session其初始值為null,第一次調(diào)用currentSession()的時(shí)候,線程局部變量的get()方法也為null。因此,對(duì)session做了判斷,如果為null,則新開(kāi)一個(gè)Session,并保存到線程局部變量session中,這一步非常的關(guān)鍵,這也是public static final ThreadLocal session = new ThreadLocal()
所創(chuàng)建的對(duì)象session通過(guò)get()
獲取的對(duì)象能強(qiáng)制轉(zhuǎn)換為Hibernate Session對(duì)象的原因。
3.3 驗(yàn)證線程變量的隔離性
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();
}
}
}
雖然三個(gè)線程都向t1對(duì)象中set()
數(shù)據(jù)值,但每個(gè)線程還是能取出自己的數(shù)據(jù)。
3.4 解決get() 返回null問(wèn)題
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 "我是默認(rèn)值 第一次get不再為null";
}
}
class run{
public static ThreadLocalExt t1 = new ThreadLocalExt();
public static void main(String[] args) {
if(t1.get() == null) {
System.out.println("從未放過(guò)值");
t1.set("我的值");
}
System.out.println(t1.get());
System.out.println(t1.get());
}
}
運(yùn)行結(jié)果:
我是默認(rèn)值 第一次get不再為null
我是默認(rèn)值 第一次get不再為null
此案例僅僅證明main線程有自己的值,那其他線程是否會(huì)有自己的初始值呢?
3.5 再次驗(yàn)證線程變量的隔離性
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();
}
}
}
運(yùn)行結(jié)果:
子線程和父線程各有各自所擁有的值;