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

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





















































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