前面我們介紹了Java當(dāng)中多個線程搶占一個共享資源的問題。但不論是同步還是重入鎖,都不能實實在在的解決資源緊缺的情況,這些方案只是靠制定規(guī)則來約束線程的行為,讓它們不再拼命的爭搶,而不是真正從實質(zhì)上解決他們對資源的需求。
在JDK 1.2當(dāng)中,引入了java.lang.ThreadLocal。它為我們提供了一種全新的思路來解決線程并發(fā)的問題。但是他的名字難免讓我們望文生義:本地線程?
什么是本地線程?
本地線程開玩笑的說:不要迷戀哥,哥只是個傳說。
其實ThreadLocal并非Thread at Local,而是LocalVariable in a Thread。
根據(jù)WikiPedia上的介紹,ThreadLocal其實是源于一項多線程技術(shù),叫做Thread Local Storage,即線程本地存儲技術(shù)。不僅僅是Java,在C++、C#、.NET、Python、Ruby、Perl等開發(fā)平臺上,該技術(shù)都已經(jīng)得以實現(xiàn)。
當(dāng)使用ThreadLocal維護變量時,它會為每個使用該變量的線程提供獨立的變量副本。也就是說,他從根本上解決的是資源數(shù)量的問題,從而使得每個線程持有相對獨立的資源。這樣,當(dāng)多個線程進行工作的時候,它們不需要糾結(jié)于同步的問題,于是性能便大大提升。但資源的擴張帶來的是更多的空間消耗,ThreadLocal就是這樣一種利用空間來換取時間的解決方案。
說了這么多,來看看如何正確使用ThreadLocal。
通過研究JDK文檔,我們知道,ThreadLocal中有幾個重要的方法:get()、set()、remove()、initailValue(),對應(yīng)的含義分別是:
返回此線程局部變量的當(dāng)前線程副本中的值、將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值、移除此線程局部變量當(dāng)前線程的值、返回此線程局部變量的當(dāng)前線程的“初始值”。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
還記得我們在第三篇的上半節(jié)引出的那個例子么?幾個線程修改同一個Student對象中的age屬性。為了保證這幾個線程能夠工作正常,我們需要對Student的對象進行同步。
下面我們對這個程序進行一點小小的改造,我們通過繼承Thread來實現(xiàn)多線程:
貌似這個程序沒什么問題。但是運行結(jié)果卻顯示:這個程序中的3個線程會拋出3個空指針異常。讀者一定感到很困惑。我明明在構(gòu)造器當(dāng)中把Student對象set進了ThreadLocal里面阿,為什么run起來之后居然在調(diào)用stuLocal.get()方法的時候得到的是NULL呢?
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
帶著這個疑問,讓我們深入到JDK的代碼當(dāng)中,去一看究竟。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
原來,在ThreadLocal中,有一個內(nèi)部類叫做ThreadLocalMap。這個ThreadLocalMap并非java.util.Map的一個實現(xiàn),而是利用java.lang.ref.WeakReference實現(xiàn)的一個鍵-值對應(yīng)的數(shù)據(jù)結(jié)構(gòu)其中,key是ThreadLocal類型,而value是Object類型,我們可以簡單的視為HashMap<ThreadLocal,Object>。
而在每一個Thread對象中,都有一個ThreadLocalMap的引用,即Thread.threadLocals。而ThreadLocal的set方法就是首先嘗試從當(dāng)前線程中取得ThreadLocalMap(以下簡稱Map)對象。如果取到的不為null,則以ThreadLocal對象自身為key,來取Map中的value。如果取不到Map對象,則首先為當(dāng)前線程創(chuàng)建一個ThreadLocalMap,然后以ThreadLocal對象自身為key,將傳入的value放入該Map中。
而get方法則是首先得到當(dāng)前線程的ThreadLocalMap對象,然后,根據(jù)ThreadLocal對象自身,取出相應(yīng)的value。當(dāng)然,如果在當(dāng)前線程中取不到ThreadLocalMap對象,則嘗試為當(dāng)前線程創(chuàng)建ThreadLocalMap對象,并以ThreadLocal對象自身為key,把initialValue()方法產(chǎn)生的對象作為value放入新創(chuàng)建的ThreadLocalMap中。
這樣,我們就明白上面的問題出在哪里:我們在main方法執(zhí)行期間,試圖在調(diào)用ThreadDemo3的構(gòu)造器時向ThreadLocal置入Student對象,而此時,以ThreadLocal對象為key,Student對象為value的Map是被放入當(dāng)前的活動線程內(nèi)的。也就是Main線程。而當(dāng)我們的3個ThreadDemo3線程運行起來以后,調(diào)用get()方法,都是試圖從當(dāng)前的活動線程中取得ThreadLocalMap對象,但當(dāng)前的活動線程顯然已經(jīng)不是Main線程了,于是,程序最終執(zhí)行了ThreadLocal原生的initialValue()方法,返回了null。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
講到這里,我想不少朋友一定已經(jīng)看出來了:ThreadLocal的initialValue()方法是需要被覆蓋的。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
于是,ThreadLocal的正確使用方法是:將ThreadLocal以內(nèi)部類的形式進行繼承,并覆蓋原來的initialValue()方法,在這里產(chǎn)生可供線程擁有的本地變量值。
這樣,我們就有了下面的正確例程:
********** 補疑 ******************
有的童鞋可能會問:“你這個Demo根本沒體現(xiàn)出來,每個線程里都有一個ThreadLocal對象;應(yīng)該是一個ThreadLocal對象對應(yīng)多個線程,你這變成了一對一,完全沒體現(xiàn)出ThreadLocal的作用。”
那么我們來看一下如何用一個ThreadLocal對象來對應(yīng)多個線程:
/**
*
* @author x-spirit
*/
public class ThreadDemo3 implements Runnable{

private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>(){

@Override
protected Student initialValue() {
return new Student();
}

};

public ThreadDemo3(){
}

public static void main(String[] args) {
ThreadDemo3 td3 = new ThreadDemo3();
Thread t1 = new Thread(td3);
Thread t2 = new Thread(td3);
Thread t3 = new Thread(td3);
t1.start();
t2.start();
t3.start();
}

@Override
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 = stuLocal.get();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());

}
}
這里,多個線程對象都使用同一個實現(xiàn)了Runnable接口的ThreadDemo3對象來構(gòu)造。這樣,多個線程使用的ThreadLocal對象就是同一個。結(jié)果仍然是正確的。但是仔細回想一下,這兩種實現(xiàn)方案有什么不同呢?
答案其實很簡單,并沒有本質(zhì)上的不同。對于第一種實現(xiàn),不同的線程對象當(dāng)中ThreadLocalMap里面的KEY使用的是不同的ThreadLocal對象。而對于第二種實現(xiàn),不同的線程對象當(dāng)中ThreadLocalMap里面的KEY是同一個ThreadLocal對象。但是從本質(zhì)上講,不同的線程對象都是利用其自身的ThreadLocalMap對象來對各自的Student對象進行封裝,用ThreadLocal對象作為該ThreadLocalMap的KEY。所以說,“ThreadLocal的思想精髓就是為每個線程創(chuàng)建獨立的資源副本。”這句話并不應(yīng)當(dāng)被理解成:一定要使用同一個ThreadLocal對象來對多個線程進行處理。因為真正用來封裝變量的不是ThreadLocal。就算是你的程序中所有線程都共用同一個ThreadLocal對象,而你真正封裝到ThreadLocalMap中去的仍然是.hashCode()方法返回不同值的不同對象。就好比線程就是房東,ThreadLocalMap就是房東的房子。房東通過ThreadLocal這個中介去和房子里的房客打交道,而房東不管要讓房客住進去還是搬出來,都首先要經(jīng)過ThreadLocal這個中介。
所以提到ThreadLocal,我們不應(yīng)當(dāng)顧名思義的認為JDK里面提供ThreadLocal就是提供了一個用來封裝本地線程存儲的容器,它本身并沒有Map那樣的容器功能。真正發(fā)揮作用的是ThreadLocalMap。也就是說,事實上,采用ThreadLocal來提高并發(fā)行,首先要理解,這不是一種簡單的對象封裝,而是一套機制,而這套機制中的三個關(guān)鍵因素(Thread、ThreadLocal、ThreadLocalMap)之間的關(guān)系是值得我們引起注意的。
**************** 補疑完畢 ***************************
可見,要正確使用ThreadLocal,必須注意以下幾點:
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
1. 總是對ThreadLocal中的initialValue()方法進行覆蓋。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
2. 當(dāng)使用set()或get()方法時牢記這兩個方法是對當(dāng)前活動線程中的ThreadLocalMap進行操作,一定要認清哪個是當(dāng)前活動線程!
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
3. 適當(dāng)?shù)氖褂梅盒停梢詼p少不必要的類型轉(zhuǎn)換以及可能由此產(chǎn)生的問題。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
運行該程序,我們發(fā)現(xiàn):程序的執(zhí)行過程只需要5秒,而如果采用同步的方法,程序的執(zhí)行結(jié)果相同,但執(zhí)行時間需要15秒。以前是多個線程為了爭取一個資源,不得不在同步規(guī)則的制約下互相謙讓,浪費了一些時間。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
現(xiàn)在,采用ThreadLocal機制以后,可用的資源多了,你有我有全都有,所以,每個線程都可以毫無顧忌的工作,自然就提高了并發(fā)性,線程安全也得以保證。
當(dāng)今很多流行的開源框架也采用ThreadLocal機制來解決線程的并發(fā)問題。比如大名鼎鼎的 Struts 2.x 和 Spring 等。
把ThreadLocal這樣的話題放在我們的同步機制探討中似乎顯得不是很合適。但是ThreadLocal的確為我們解決多線程的并發(fā)問題帶來了全新的思路。它為每個線程創(chuàng)建一個獨立的資源副本,從而將多個線程中的數(shù)據(jù)隔離開來,避免了同步所產(chǎn)生的性能問題,是一種“以空間換時間”的解決方案。
但這并不是說ThreadLocal就是包治百病的萬能藥了。如果實際的情況不允許我們?yōu)槊總€線程分配一個本地資源副本的話,同步還是非常有意義的。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
好了,本系列到此馬上就要劃上一個圓滿的句號了。不知大家有什么意見和疑問沒有。希望看到你們的留言。
下一講中我們就來對之前的內(nèi)容進行一個總結(jié),順便討論一下被遺忘的volatile關(guān)鍵字。敬請期待。
在JDK 1.2當(dāng)中,引入了java.lang.ThreadLocal。它為我們提供了一種全新的思路來解決線程并發(fā)的問題。但是他的名字難免讓我們望文生義:本地線程?
什么是本地線程?
本地線程開玩笑的說:不要迷戀哥,哥只是個傳說。
其實ThreadLocal并非Thread at Local,而是LocalVariable in a Thread。
根據(jù)WikiPedia上的介紹,ThreadLocal其實是源于一項多線程技術(shù),叫做Thread Local Storage,即線程本地存儲技術(shù)。不僅僅是Java,在C++、C#、.NET、Python、Ruby、Perl等開發(fā)平臺上,該技術(shù)都已經(jīng)得以實現(xiàn)。
當(dāng)使用ThreadLocal維護變量時,它會為每個使用該變量的線程提供獨立的變量副本。也就是說,他從根本上解決的是資源數(shù)量的問題,從而使得每個線程持有相對獨立的資源。這樣,當(dāng)多個線程進行工作的時候,它們不需要糾結(jié)于同步的問題,于是性能便大大提升。但資源的擴張帶來的是更多的空間消耗,ThreadLocal就是這樣一種利用空間來換取時間的解決方案。
說了這么多,來看看如何正確使用ThreadLocal。
通過研究JDK文檔,我們知道,ThreadLocal中有幾個重要的方法:get()、set()、remove()、initailValue(),對應(yīng)的含義分別是:
返回此線程局部變量的當(dāng)前線程副本中的值、將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值、移除此線程局部變量當(dāng)前線程的值、返回此線程局部變量的當(dāng)前線程的“初始值”。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
還記得我們在第三篇的上半節(jié)引出的那個例子么?幾個線程修改同一個Student對象中的age屬性。為了保證這幾個線程能夠工作正常,我們需要對Student的對象進行同步。
下面我們對這個程序進行一點小小的改造,我們通過繼承Thread來實現(xiàn)多線程:
/**
*
* @author x-spirit
*/
public class ThreadDemo3 extends Thread{
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>();
public ThreadDemo3(Student stu){
stuLocal.set(stu);
}
public static void main(String[] args) {
Student stu = new Student();
ThreadDemo3 td31 = new ThreadDemo3(stu);
ThreadDemo3 td32 = new ThreadDemo3(stu);
ThreadDemo3 td33 = new ThreadDemo3(stu);
td31.start();
td32.start();
td33.start();
}
@Override
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 = stuLocal.get();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
}
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/ *
* @author x-spirit
*/
public class ThreadDemo3 extends Thread{
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>();
public ThreadDemo3(Student stu){
stuLocal.set(stu);
}
public static void main(String[] args) {
Student stu = new Student();
ThreadDemo3 td31 = new ThreadDemo3(stu);
ThreadDemo3 td32 = new ThreadDemo3(stu);
ThreadDemo3 td33 = new ThreadDemo3(stu);
td31.start();
td32.start();
td33.start();
}
@Override
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 = stuLocal.get();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
}
貌似這個程序沒什么問題。但是運行結(jié)果卻顯示:這個程序中的3個線程會拋出3個空指針異常。讀者一定感到很困惑。我明明在構(gòu)造器當(dāng)中把Student對象set進了ThreadLocal里面阿,為什么run起來之后居然在調(diào)用stuLocal.get()方法的時候得到的是NULL呢?
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
帶著這個疑問,讓我們深入到JDK的代碼當(dāng)中,去一看究竟。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
原來,在ThreadLocal中,有一個內(nèi)部類叫做ThreadLocalMap。這個ThreadLocalMap并非java.util.Map的一個實現(xiàn),而是利用java.lang.ref.WeakReference實現(xiàn)的一個鍵-值對應(yīng)的數(shù)據(jù)結(jié)構(gòu)其中,key是ThreadLocal類型,而value是Object類型,我們可以簡單的視為HashMap<ThreadLocal,Object>。
而在每一個Thread對象中,都有一個ThreadLocalMap的引用,即Thread.threadLocals。而ThreadLocal的set方法就是首先嘗試從當(dāng)前線程中取得ThreadLocalMap(以下簡稱Map)對象。如果取到的不為null,則以ThreadLocal對象自身為key,來取Map中的value。如果取不到Map對象,則首先為當(dāng)前線程創(chuàng)建一個ThreadLocalMap,然后以ThreadLocal對象自身為key,將傳入的value放入該Map中。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
return t.threadLocals;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
而get方法則是首先得到當(dāng)前線程的ThreadLocalMap對象,然后,根據(jù)ThreadLocal對象自身,取出相應(yīng)的value。當(dāng)然,如果在當(dāng)前線程中取不到ThreadLocalMap對象,則嘗試為當(dāng)前線程創(chuàng)建ThreadLocalMap對象,并以ThreadLocal對象自身為key,把initialValue()方法產(chǎn)生的對象作為value放入新創(chuàng)建的ThreadLocalMap中。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
這樣,我們就明白上面的問題出在哪里:我們在main方法執(zhí)行期間,試圖在調(diào)用ThreadDemo3的構(gòu)造器時向ThreadLocal置入Student對象,而此時,以ThreadLocal對象為key,Student對象為value的Map是被放入當(dāng)前的活動線程內(nèi)的。也就是Main線程。而當(dāng)我們的3個ThreadDemo3線程運行起來以后,調(diào)用get()方法,都是試圖從當(dāng)前的活動線程中取得ThreadLocalMap對象,但當(dāng)前的活動線程顯然已經(jīng)不是Main線程了,于是,程序最終執(zhí)行了ThreadLocal原生的initialValue()方法,返回了null。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
講到這里,我想不少朋友一定已經(jīng)看出來了:ThreadLocal的initialValue()方法是需要被覆蓋的。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
于是,ThreadLocal的正確使用方法是:將ThreadLocal以內(nèi)部類的形式進行繼承,并覆蓋原來的initialValue()方法,在這里產(chǎn)生可供線程擁有的本地變量值。
這樣,我們就有了下面的正確例程:
/**
*
* @author x-spirit
*/
public class ThreadDemo3 extends Thread{
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>(){
@Override
protected Student initialValue() {
return new Student();
}
};
public ThreadDemo3(){
}
public static void main(String[] args) {
ThreadDemo3 td31 = new ThreadDemo3();
ThreadDemo3 td32 = new ThreadDemo3();
ThreadDemo3 td33 = new ThreadDemo3();
td31.start();
td32.start();
td33.start();
}
@Override
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 = stuLocal.get();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
}
*
* @author x-spirit
*/
public class ThreadDemo3 extends Thread{
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>(){
@Override
protected Student initialValue() {
return new Student();
}
};
public ThreadDemo3(){
}
public static void main(String[] args) {
ThreadDemo3 td31 = new ThreadDemo3();
ThreadDemo3 td32 = new ThreadDemo3();
ThreadDemo3 td33 = new ThreadDemo3();
td31.start();
td32.start();
td33.start();
}
@Override
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 = stuLocal.get();
student.setAge(age);
System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
}
}
********** 補疑 ******************
有的童鞋可能會問:“你這個Demo根本沒體現(xiàn)出來,每個線程里都有一個ThreadLocal對象;應(yīng)該是一個ThreadLocal對象對應(yīng)多個線程,你這變成了一對一,完全沒體現(xiàn)出ThreadLocal的作用。”
那么我們來看一下如何用一個ThreadLocal對象來對應(yīng)多個線程:





















































這里,多個線程對象都使用同一個實現(xiàn)了Runnable接口的ThreadDemo3對象來構(gòu)造。這樣,多個線程使用的ThreadLocal對象就是同一個。結(jié)果仍然是正確的。但是仔細回想一下,這兩種實現(xiàn)方案有什么不同呢?
答案其實很簡單,并沒有本質(zhì)上的不同。對于第一種實現(xiàn),不同的線程對象當(dāng)中ThreadLocalMap里面的KEY使用的是不同的ThreadLocal對象。而對于第二種實現(xiàn),不同的線程對象當(dāng)中ThreadLocalMap里面的KEY是同一個ThreadLocal對象。但是從本質(zhì)上講,不同的線程對象都是利用其自身的ThreadLocalMap對象來對各自的Student對象進行封裝,用ThreadLocal對象作為該ThreadLocalMap的KEY。所以說,“ThreadLocal的思想精髓就是為每個線程創(chuàng)建獨立的資源副本。”這句話并不應(yīng)當(dāng)被理解成:一定要使用同一個ThreadLocal對象來對多個線程進行處理。因為真正用來封裝變量的不是ThreadLocal。就算是你的程序中所有線程都共用同一個ThreadLocal對象,而你真正封裝到ThreadLocalMap中去的仍然是.hashCode()方法返回不同值的不同對象。就好比線程就是房東,ThreadLocalMap就是房東的房子。房東通過ThreadLocal這個中介去和房子里的房客打交道,而房東不管要讓房客住進去還是搬出來,都首先要經(jīng)過ThreadLocal這個中介。
所以提到ThreadLocal,我們不應(yīng)當(dāng)顧名思義的認為JDK里面提供ThreadLocal就是提供了一個用來封裝本地線程存儲的容器,它本身并沒有Map那樣的容器功能。真正發(fā)揮作用的是ThreadLocalMap。也就是說,事實上,采用ThreadLocal來提高并發(fā)行,首先要理解,這不是一種簡單的對象封裝,而是一套機制,而這套機制中的三個關(guān)鍵因素(Thread、ThreadLocal、ThreadLocalMap)之間的關(guān)系是值得我們引起注意的。
**************** 補疑完畢 ***************************
可見,要正確使用ThreadLocal,必須注意以下幾點:
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
1. 總是對ThreadLocal中的initialValue()方法進行覆蓋。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
2. 當(dāng)使用set()或get()方法時牢記這兩個方法是對當(dāng)前活動線程中的ThreadLocalMap進行操作,一定要認清哪個是當(dāng)前活動線程!
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
3. 適當(dāng)?shù)氖褂梅盒停梢詼p少不必要的類型轉(zhuǎn)換以及可能由此產(chǎn)生的問題。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
運行該程序,我們發(fā)現(xiàn):程序的執(zhí)行過程只需要5秒,而如果采用同步的方法,程序的執(zhí)行結(jié)果相同,但執(zhí)行時間需要15秒。以前是多個線程為了爭取一個資源,不得不在同步規(guī)則的制約下互相謙讓,浪費了一些時間。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
現(xiàn)在,采用ThreadLocal機制以后,可用的資源多了,你有我有全都有,所以,每個線程都可以毫無顧忌的工作,自然就提高了并發(fā)性,線程安全也得以保證。
當(dāng)今很多流行的開源框架也采用ThreadLocal機制來解決線程的并發(fā)問題。比如大名鼎鼎的 Struts 2.x 和 Spring 等。
把ThreadLocal這樣的話題放在我們的同步機制探討中似乎顯得不是很合適。但是ThreadLocal的確為我們解決多線程的并發(fā)問題帶來了全新的思路。它為每個線程創(chuàng)建一個獨立的資源副本,從而將多個線程中的數(shù)據(jù)隔離開來,避免了同步所產(chǎn)生的性能問題,是一種“以空間換時間”的解決方案。
但這并不是說ThreadLocal就是包治百病的萬能藥了。如果實際的情況不允許我們?yōu)槊總€線程分配一個本地資源副本的話,同步還是非常有意義的。
轉(zhuǎn)載注明出處:http://x- spirit.javaeye.com/、http: //www.aygfsteel.com/zhangwei217245/
好了,本系列到此馬上就要劃上一個圓滿的句號了。不知大家有什么意見和疑問沒有。希望看到你們的留言。
下一講中我們就來對之前的內(nèi)容進行一個總結(jié),順便討論一下被遺忘的volatile關(guān)鍵字。敬請期待。
這只是一個DEMO,使用new在這里其實就是一個打比方的方法。如果我用StudentFactory也許更容易接受一點,呵呵。
在實際的應(yīng)用中,我們經(jīng)常可以看到一些用ThreadLocal來封裝資源的例子。他們無一例外的都是做一件事情:創(chuàng)建新的資源,供線程使用。例如用ThreadLocal來處理JDBC 的Connection。即使你沒有覆蓋initialValue()方法,而是用先get再判空,再set的方式,也還是為一個沒有獲取到connection的線程創(chuàng)建一個connection。
所以,問題的關(guān)鍵不在于ThreadLocal使用的時候采用何種形式。ThreadLocal的思想精髓就是為每個線程創(chuàng)建獨立的資源副本。而使用ThreadLocal的時候最最重要的就是分清楚當(dāng)前活動線程是哪個。
至于覆蓋initialValue()方法的問題,這個應(yīng)該是仁者見仁,智者見智的問題,我只是提出一個能夠節(jié)約代碼量的方案。一般情況下,覆蓋initialValue()方法已經(jīng)可以解決問題,這是一種最為經(jīng)濟的編碼習(xí)慣,它不僅能夠達到要求,并且和JDK的原生API結(jié)合的很好,不容易出錯,當(dāng)然如果你需要在用ThreadLocal處理資源之前做一些其他的處理,那就另當(dāng)別論了。
謝謝夸獎啦
這句話說的好,ThreadLocal的思想精髓就是為每個線程創(chuàng)建獨立的資源副本。你這個Demo根本沒體現(xiàn)出來,每個線程里都有一個ThreadLocal對象;應(yīng)該是一個ThreadLocal對象對應(yīng)多個線程,你這變成了一對一,完全沒體現(xiàn)出ThreadLocal的作用。
感謝你的關(guān)注。關(guān)于應(yīng)用場景的問題,我想沒有什么非常好的例子可以給你。不過只能告訴你使用ThreadLocal的好處就是可以避免同步帶來的性能損耗,并且,當(dāng)多個線程同時使用同一個類的實例的時候,如果這個實例不是單例模式的一個實現(xiàn),那么ThreadLocal就是值得考慮的。
謝謝你的回復(fù),期待你的下一篇文章。
呵呵,這個系列基本上要結(jié)束了。但是由于我最近比較忙,所以本系列的總結(jié)還沒有時間整理。不過過些日子會整理出來的。請保持關(guān)注。
謝謝樓主
了解了鎮(zhèn)面貌了