理解ThreadLocal類

來源:互聯網
上載者:User

標籤:threadlocal

1 ThreadLocal是什麼 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程式的並發問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優美的多線程程式。 ThreadLocal,顧名思義,它不是一個線程,而是線程的一個本地化對象。當工作於多線程中的對象使用ThreadLocal維護變數時,ThreadLocal為每個使用該變數的線程分配一個獨立的變數副本。所以每一個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。從線程的角度看,這個變數就像是線程的本地變數,這也是類名中“Local”所要表達的意思。
API的解釋:該類提供了 線程局部 (thread-local) 變數。這些變數不同於它們的普通對應物,因為訪問某個變數(通過其 get 或 set 方法)的每個線程都有自己的局部變數,它獨立於變數的初始化副本。 ThreadLocal 執行個體通常是類中的 private static 欄位它們希望將狀態與某一個線程(例如,使用者識別碼 或事務 ID)相關聯
API提供的使用例子:
import java.util.concurrent.atomic.AtomicInteger;public class ThreadId {// Atomic integer containing the next thread ID to be assignedprivate static final AtomicInteger nextId = new AtomicInteger(0);// Thread local variable containing each thread's IDprivate static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return nextId.getAndIncrement();}};// Returns the current thread's unique ID, assigning it if necessarypublic static int get() {return threadId.get();}}
每個線程都保持對其線程局部變數副本的隱式引用,只要線程是活動的並且 ThreadLocal 執行個體是可訪問的;線上程消失之後,其線程局部執行個體的所有副本都會被記憶體回收(除非存在對這些副本的其他引用)。
2 ThreadLocal類的實現與方法介紹 ThreadLocal類有4個方法如下void set(Object value) :設定當前線程的線程局部變數的值;
public Object get()       :該方法返回當前線程所對應的線程局部變數;
public void remove()    :將當前線程局部變數的值刪除,目的是為了減少記憶體的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束後,對應該線程的局部變數將自動被記憶體回收,所以顯式調用該方法清除線程的局部變數並不是必須的操作,但它可以加快記憶體回收的速度;
protected Object initialValue()  :返回該線程局部變數的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,線上程第1次調用get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的預設實現直接返回一個null。 
Java語言中,如果一個變數要被多線程訪問,可以使用volatile關鍵字聲明它為“易變的”;如果一個變數要被某個線程獨享,Java中就沒有類似C++中_declspec(thread)這樣的關鍵字,不過還是可以使用ThreadLocal類來實現執行緒區域儲存的功能。 每一個線程的Thread對象中都有一個ThreadLocalMap對象,這個Object Storage Service了一組以ThreadLocal.threadLocalHashCode為鍵,以本地線程變數為值的K-V值對,ThreadLocal對象就是當前線程的ThreadLocalMap訪問入口,每一個ThreadLocal對象都包含了一個獨一無二的threadLocalHashCode值,使用這個值就可以線上程K-V值對中找回對應的本地線程變數。
ThreadLocal是如何做到為每一個線程維護變數的副本的呢?其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於儲存每一個線程的變數副本,Map中元素的鍵為線程對象,而值對應線程的變數副本。
簡單的實現版本(和jdk的實現思路類似):
public class SimpleThreadLocal {private final Map valueMap = Collections.synchronizedMap(new HashMap());public void set(Object newValue) {//鍵為線程對象,值為本線程的變數副本valueMap.put(Thread.currentThread(), newValue);}public Object get() {Thread currentThread = Thread.currentThread();//返回本線程對應的變數Object o = valueMap.get(currentThread);//如果在Map中不存在,放到Map中儲存起來if (o == null && !valueMap.containsKey(currentThread)) {o = initialValue();valueMap.put(currentThread, o);}return o;}public void remove() {valueMap.remove(Thread.currentThread());}public Object initialValue() {return null;}}

3  一個多線程例子通過一個具體的執行個體瞭解一下ThreadLocal的具體使用方法
public class SequenceNumber {// 通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {@Overridepublic Integer initialValue() {return 0;}};// 擷取下一個序列值public int getNextNum() {seqNum.set(seqNum.get() + 1);return seqNum.get();}public static void main(String[] args) {SequenceNumber sn = new SequenceNumber();// 3個線程共用sn,各自產生序號TestClient t1 = new TestClient(sn);TestClient t2 = new TestClient(sn);TestClient t3 = new TestClient(sn);t1.start();t2.start();t3.start();}private static class TestClient extends Thread {private final SequenceNumber sn;public TestClient(SequenceNumber sn) {this.sn = sn;}@Overridepublic void run() {//每個線程打出3個序列值for (int i = 0; i < 3; i++) {System.out.println("thread[" + Thread.currentThread().getName()+ "] sn[" + sn.getNextNum() + "]");}}}}
考查下面輸出的結果資訊,我們發現每個線程所產生的序號雖然都共用同一個Sequence Number執行個體,但它們並沒有發生相互幹擾的情況,而是各自產生獨立的序號,這是因為我們通過ThreadLocal為每一個線程提供了單獨的副本。 
thread[Thread-1] sn[1]thread[Thread-1] sn[2]thread[Thread-0] sn[1]thread[Thread-2] sn[1]thread[Thread-0] sn[2]thread[Thread-0] sn[3]thread[Thread-1] sn[3]thread[Thread-2] sn[2]thread[Thread-2] sn[3]
4  總結ThreadLocal本質上是從另一個角度來解決多線程的並發訪問。ThreadLocal為每一個線程提供一個獨立的變數副本,從而隔離了多個線程對訪問資料的衝突。因為每一個線程都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。 ThreadLocal提供了安全執行緒的對象封裝,在編寫多線程代碼時,可以把不安全的變數封裝進ThreadLocal。 概括起來說,對於多線程資源共用的問題, 同步機制採用了“以時間換空間”的方式:訪問序列化,對象共用化。而 ThreadLocal採用了“以空間換時間”的方式:訪問並行化,對象獨享化。前者僅提供一份變數,讓不同的線程排隊訪問,而後者為每一個線程都提供了一份變數,因此可以同時訪問而互不影響。 

參考書目:深入理解JVM,Spring3.x開發實戰

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.