標籤:獨立 自己 要求 src 佔用 get 內部資料 源碼 map對象
線程封閉:當訪問共用的可變資料時,通常需要同步。一種避免同步的方式就是不共用資料。如果僅在單線程內訪問資料,就不需要同步,這種技術稱為線程封閉(thread confinement)
線程封閉技術一個常見的應用就是JDBC的Connection對象,JDBC規範並沒有要求Connection對象必須是安全執行緒的,在伺服器應用程式中,線程從串連池擷取一個Connection對象,使用完之後將對象返還給串連池。下面介紹幾種線程封閉技術:
1、Ad-hoc線程封閉
Ad-hoc線程封閉是指,維護線程的封閉性的職責完全由程式實現承擔,是非常脆弱的,因此在程式中盡量少使用,一般使用更強的線程封閉技術,比如棧封閉或者ThreadLocal類。
2、棧封閉
棧封閉是線程封閉的一種特列,在棧封閉中,只能通過局部變數才能訪問對象。局部變數的固有屬性之一就是封閉在執行棧中,其他線程無法訪問這個棧,棧封閉也稱為線程內部使用或者線程局部使用。簡單的說就是局部變數。多個線程訪問一個方法,此方法中的局部變數都會被拷貝一分兒到線程棧中。所以局部變數是不被多個線程所共用的,也就不會出現並發問題。所以能用局部變數就別用全域的變數,全域變數容易引起並發問題。
比如下面的例子:
View Code
在loadTheArk中執行個體化一個TreeSet對象,並將該對象的一個引用儲存到animals中。此時,只有一個引用指向集合animals,這個引用被封閉到局部變數中,因此也被封閉到局部變數中。然而,如果發布了對集合animals(或者該對象中的任何內部資料)的引用,那麼封閉性將被破壞,並導致對象animals的逸出。
3、ThreadLocal類
維持線程封閉性的一種更加規範方法是使用ThreadLocal類,這個類能使線程中某個值與儲存值的對象關聯起來。ThreadLocal類提供了get和set等提供者或者方法,這些方法為每個使用該變數的線程都存在一份獨立的副本,因此get總是放回當前執行線程在調用set設定的最新值。看一下下面代碼例子:
View Code
通過調用ConnectionManager.getConnection()方法,每個線程擷取到的,都是自己獨立擁有的一個的Connection對象副本,第一次擷取時,是通過initialValue()方法的傳回值來設定值的。通過ConnectionManager.setConnection(Connection conn)方法設定的Connection對象,也只會和當前線程綁定。這樣就實現了Connection對象在多個線程中的完全隔離。在Spring容器中管理多線程環境下的Connection對象時,採用的思路和以上代碼非常相似。
每個線程是怎麼和Connection對象副本綁定的?這個對象副本儲存在哪裡。當某個線程初次調用ThreadLocal類的get方法時,就會調用initialValue來擷取初始值,從概念上看,我們可以將ThreadLocal<T>視為包含了Map<thread, T>對象,其中儲存了特定於該線程的值,但是ThreadLocal的實現並非如此,這樣只是為了我們方便理解而已。
下面我們來分析一下ThreadLocal類的源碼。ThreadLocal類的方法很簡單,只有四個,分別為set,get,remove, initialValue,從字面上我們也能理解這些方法的作用。
public T get():返回當前線程所對應的局部變數。
public void set(T arg0):設定當前線程局部變數的值。
public void remove():將當前線程局部變數的值刪除,目的是為了減少記憶體的佔用,該方法是JDK 5.0新增的方法。注意,當線程結束後,對應該線程的局部變數將自動被記憶體回收,所以顯式調用該方法清除線程的局部變數並不是必須的操作,但它可以加快記憶體回收的速度。
protected T initialValue(): 對當線程局部變數進行初始化,並返回該初始值。是protected 屬性,顯然是讓子類進行對其覆蓋重寫的,只有第一次調用set和get方法時才調用。
下面我們對這四個方法的源碼進行分析,看看ThreadLocal類是如何?這種“為每個線程提供不同的變數拷貝”。
3.1 set方法
以下是set方法的源碼
View Code
從set方法中可以看到,首先擷取當前線程:Thread arg1 = Thread.currentThread();
再擷取當前線程的ThreadLocalMap:ThreadLocal.ThreadLocalMap arg2 = this.getMap(arg1);
判斷ThreadLocalMap是否為空白,不為空白,則以索引值對的形式設定值,key為this,value就是局部變數的副本,this是當前線程持有的ThreadLocal類執行個體化對象。
假如為空白,則通過createMap方法建立。
我們看下getMap和createMap方法的源碼:
View Code
從代碼上已經寫的非常清楚,每個線程都有自己的局部變數的副本,該副本是存在ThreadLocalMap 中,其中索引值就是ThreadLocal類執行個體化對象。也就是說每個線程都擁有自己的ThreadLocalMap,ThreadLocalMap儲存的就是局部變數副本。我們看一下java.lang.Thread源碼。
View Code
3.2 get方法
View Code
從代碼上看,前兩步和set方法是一個樣的,分別擷取當前線程和當前線程的ThreadLocalMap,第三步判斷ThreadLocalMap是否為空白,不為空白根據this索引值擷取value,為空白調用setInitialValue()方法。
以下是setInitialValue方法代碼:
View Code
在setInitialValue裡調用了initialValue()方法,也就是子類要重寫覆蓋的方法,對應上面的例子的代碼是:
View Code
然後擷取當前線程和當前線程的ThreadLocalMap,ThreadLocalMap為空白則調用createMap,否則調用set方法。
3.3 總結
ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵進行尋找的,這當然和前面set()方法的代碼是相呼應的。
進一步地,我們可以建立不同的ThreadLocal執行個體來實現多個變數在不同線程間的訪問隔離,為什麼可以這麼做?因為不同的ThreadLocal對象作為不同鍵,當然也可以線上程的ThreadLocalMap對象中設定不同的值了。通過ThreadLocal對象,在多線程中共用一個值和多個值的區別,就像你在一個HashMap對象中儲存一個索引值對和多個索引值對一樣,僅此而已。
也就說,每個線程都有一個ThreadLocalMap,該線程訪問到某個局部變數,且該局部變數是用ThreadLocal類進行聲明時,該線程就會new ThreadLocal(),然後將該ThreadLocal類的對象作為key值,所對應的局部變數作為value值儲存到ThreadLocalMap中。當線程訪問多個ThreadLocal類進行聲明局部變數時,在ThreadLocalMap中就有多個索引值對。而每個線程都有自己的ThreadLocalMap,從而達到隔離的目的了。
當某個線程終止後,該線程裡的ThreadLocalMap也被回收了,所以完全不用擔心記憶體流失的問題。
假如多線程訪問的對象執行個體是單例的,或者說只能建立一個,那就老老實實的使用同步機制(synchronized)了.
Java多線程——線程封閉