ThreadLocal共用線程局部變數和線程同步機制的區別,threadlocal線程
ThreadLocal是解決安全執行緒問題一個很好的思路,它通過為每個線程提供一個獨立的變數副本解決了變數並發訪問的衝突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決安全執行緒問題更簡單,更方便,且結果程式擁有更高的並發性。
對於多線程資源共用的問題,同步機制採用了“以時間換空間”的方式,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變數,讓不同的線程排隊訪問,而後者為每一個線程都提供了一份變數,因此可以同時訪問而互不影響。
ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。
1:同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之間進行通訊的有效方式;
2:而threadLocal是隔離多個線程的資料共用,從根本上就不在多個線程之間共用變數,這樣當然不需要對多個線程進行同步了。
import java.util.Random;public class ThreadSocpeShareData {static ThreadLocal<Integer> t = new ThreadLocal<Integer>(); public static void main(String[] args) { for(int i=0;i<3;i++){ new Thread(new Runnable() { @Override public void run() { int data = new Random().nextInt(); System.out.println(Thread.currentThread().getName() +" has put "+ data); t.set(data); MyThreadScopeData.getInstance().setName("name" + data); MyThreadScopeData.getInstance().setAge("age"+data); new A().get(); new B().get(); } }).start(); }} static class A{ public void get(){ int data = t.get(); MyThreadScopeData myData = MyThreadScopeData.getInstance(); System.out.println("A " + Thread.currentThread().getName() +" "+ data + myData.getAge() + myData.getName() /*ms.getName()*/); } } static class B{ public void get(){ int data = t.get(); System.out.println("B " + Thread.currentThread().getName()+ " "+ data); } }}class MyThreadScopeData{private MyThreadScopeData(){}private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();public static MyThreadScopeData getInstance(){MyThreadScopeData instance = map.get();if(instance == null){instance = new MyThreadScopeData();map.set(instance);}return instance;}private String name;private String age;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}}
事實上,我們向ThreadLocal中set的變數不是由ThreadLocal來儲存的,而是Thread線程對象自身儲存。當使用者調用ThreadLocal對象的set(Object o)時,該方法則通過Thread.currentThread()擷取當前線程,將變數存入Thread中的一個Map內,而Map的Key就是當前的ThreadLocal執行個體。請看源碼,這是最主要的兩個函數,能看出ThreadLocal與Thread的調用關係:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 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); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
具體可以看看裡面的源碼,不過有一點是可以證實的,就是Threadlocal中 建立的線程副本,可以不調用remove來做清理工作,因為jvm在發現線程的調傭不再使用時,會進行自動的記憶體回收操作,我以前寫程式在使用Threadlocal時卻是經常的進行使用完成之後的清理工作。(防止佔用記憶體,如果建立的副本數量不是太多的話,可以讓虛擬機器自動來清除)
ThreadLocal的使用方法
早在Java 1.2推出之時,Java平台中就引入了一個新的支援:java.lang.ThreadLocal,給我們在編寫多線程程式時提供了一種新的選擇。使用這個工具類可以很簡潔地編寫出優美的多線程程式,雖然ThreadLocal非常有用,但是似乎現在瞭解它、使用它的朋友還不多。
ThreadLocal是什麼
ThreadLocal是什麼呢?其實ThreadLocal並非是一個線程的本地實現版本,它並不是一個Thread,而是thread local variable(線程局部變數)。也許把它命名為ThreadLocalVar更加合適。線程局部變數(ThreadLocal)其實的功用非常簡單,就是為每一個使用該變數的線程都提供一個變數值的副本,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本衝突。從線程的角度看,就好像每一個線程都完全擁有該變數。線程局部變數並不是Java的新發明,在其它的一些語言編譯器實現(如IBM XL FORTRAN)中,它在語言的層次提供了直接的支援。因為Java中沒有提供在語言層次的直接支援,而是提供了一個ThreadLocal的類來提供支援,所以,在Java中編寫線程局部變數的代碼相對比較笨拙,這也許是線程局部變數沒有在Java中得到很好的普及的一個原因吧。
ThreadLocal的設計
首先看看ThreadLocal的介面:
Object get() ; // 返回當前線程的線程局部變數副本 protected Object initialValue(); // 返回該線程局部變數的當前線程的初始值
void set(Object value); // 設定當前線程的線程局部變數副本的值
ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected的方法,顯然是為了子類重寫而特意實現的。該方法返回當前線程在該線程局部變數的初始值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行,並且僅執行1次。ThreadLocal中的確實實現直接返回一個null:
protected Object initialValue() { return null; }
ThreadLocal是如何做到為每一個線程維護變數的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於儲存每一個線程的變數的副本。比如下面的樣本實現:
public class ThreadLocal
{
private Map values = Collections.synchronizedMap(new HashMap());
public Object get()
{
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread))
{
o = initialValue();
values.put(curThread, o);
}
return o;
}
public void set(Obj......餘下全文>>
多線程中,共用的變數為何在main()裡必須用final修飾
問題提的很好,搜了一把終於知道了大概的原因,首先糾正提問者的提問,貼出來的第一段代碼屬於匿名內部類,第二端代碼不屬於匿名內部類。
回答你的問題,為什麼在匿名內部類中引用外部對象要加final修飾符呢,因為,在匿名內部類中引用的外部對象受到外部線程的範圍的制約有其特定的生命週期,以線程為例,當外部的變數生命週期已經完結之後,內部的線程還在運行,怎麼樣解決這個外部生命週期已經結束而在內部卻需要繼續使用呢,這個時候就需要在外部變數中添加final修飾符,其實內部匿名類使用的這個變數就是外部變數的一個“複製品”,即使外部變數生命週期已經結束,內部的“複製品“依然可用。
網路搜尋的答案如下:
為什麼匿名內部類參數必須為final類型
1) 從程式設計語言的理論上:局部內部類(即:定義在方法中的內部類),由於本身就是在方法內部(可出現在形式參數定義處或者方法體處),因而存取方法中的局部變數(形式參數或局部變數)是天經地義的.是很自然的
2) 為什麼JAVA中要加上一條限制:只能訪問final型的局部變數?
3) JAVA語言的編譯器的設計者當然全實現:局部內部類能存取方法中的所有的局部變數(因為:從理論上這是很自然的要求),但是:編譯技術是無法實現的或代價極高.
4) 困難在何處?到底難在哪兒?
局部變數的生命週期與局部內部類的對象的生命週期的不一致性!
5) 設方法f被調用,從而在它的調用棧中產生了變數i,此時產生了一個局部內部類對象inner_object,它訪問了該局部變數i .當方法f()運行結束後,局部變數i就已死亡了,不存在了.但:局部內部類對象inner_object還可能 一直存在(只能沒有人再引用該對象時,它才會死亡),它不會隨著方法f()運行結束死亡.這時:出現了一個"荒唐"結果:局部內部類對象 inner_object要訪問一個已不存在的局部變數i!
6) 如何才能實現?當變數是final時,通過將final局部變數"複製"一份,複製品直接作為局部內部中的資料成員.這樣:當局部內部類訪問局部變數 時,其實真正訪問的是這個局部變數的"複製品"(即:這個複製品就代表了那個局部變數).因此:當運行棧中的真正的局部變數死亡時,局部內部類對象仍可以 訪問局部變數(其實訪問的是"複製品"),給人的感覺:好像是局部變數的"生命期"延長了.
那麼:核心的問題是:怎麼才能使得:訪問"複製品"與訪問真正的原始的局部變數,其語義效果是一樣的呢?
當變數是final時,若是基礎資料型別 (Elementary Data Type),由於其值不變,因而:其複製品與原始的量是一樣.語義效果相同.(若:不是final,就無法保證:複製品與原始變數保持一致了,因為:在方法中改的是原始變數,而局部內部類中改的是複製品)
當 變數是final時,若是參考型別,由於其引用值不變(即:永遠指向同一個對象),因而:其複製品與原始的引用變數一樣,永遠指向同一個對象(由於是 final,從而保證:只能指向這個對象,再不能指向其它對象),達到:局部內部類中訪問的複製品與方法代碼中訪問的原始對象,永遠都是同一個即:語義效 果是一樣的.否則:當方法中改原始變數,而局部內部類中改複製品時,就無法保證:複製品與原始變數保持一致了(因此:它們原本就應該是同一個變數.)
一句話:這個規定是一種無可奈何.也說明:程式設計語言的設計是受到實現技術的限制的.這就是一例. 因為:我就看到不少人都持這種觀點......餘下全文>>