Java並發編程實踐 目錄
並發編程 04—— 閉鎖CountDownLatch 與 柵欄CyclicBarrier
並發編程 06—— CompletionService : Executor 和 BlockingQueue
並發編程 10—— 任務取消 之 關閉 ExecutorService
並發編程 12—— 任務取消與關閉 之 shutdownNow 的局限性
並發編程 13—— 線程池的使用 之 配置ThreadPoolExecutor 和 飽和策略
並發編程 20—— AbstractQueuedSynchronizer 深入分析
概述
第1 部分 ThreadLocal是什么
ThreadLocal是什么呢?其實ThreadLocal並非是一個線程的本地實現版本,它並不是一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適。ThreadLocal功能非常簡單,就是為每一個使用該變量的線程都提供一個變量值的副本,是Java中一種較為特殊的線程綁定機制,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本沖突。
從線程的角度看,每個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的並且ThreadLocal實例是可訪問的;在線程消失之后,其線程局部實例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)。
通過ThreadLocal存取的數據,總是與當前線程相關,也就是說,JVM 為每個運行的線程,綁定了私有的本地實例存取空間,從而為多線程環境常出現的並發訪問問題提供了一種隔離機制。
第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。
set 方法:
1 /** 2 * Sets the current thread's copy of this thread-local variable 3 * to the specified value. Most subclasses will have no need to 4 * override this method, relying solely on the {@link #initialValue} 5 * method to set the values of thread-locals. 6 * 7 * @param value the value to be stored in the current thread's copy of 8 * this thread-local. 9 */ 10 public void set(T value) { 11 // 獲取當前線程對象 12 Thread t = Thread.currentThread(); 13 // 獲取當前線程本地變量Map 14 ThreadLocalMap map = getMap(t); 15 // map不為空 16 if (map != null) 17 // 存值 18 map.set(this, value); 19 else 20 // 創建一個當前線程本地變量Map 21 createMap(t, value); 22 } 23 24 /** 25 * Get the map associated with a ThreadLocal. Overridden in 26 * InheritableThreadLocal. 27 * 28 * @param t the current thread 29 * @return the map 30 */ 31 ThreadLocalMap getMap(Thread t) { 32 // 獲取當前線程的本地變量Map 33 return t.threadLocals; 34 }
這里注意,ThreadLocal中是有一個Map,但這個Map不是我們平時使用的Map,而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的一個內部類,不對外使用的。當使用ThreadLocal存值時,首先是獲取到當前線程對象,然后獲取到當前線程本地變量Map,最后將當前使用的ThreadLocal和傳入的值放到Map中,也就是說ThreadLocalMap中存的值是[ThreadLocal對象, 存放的值],這樣做的好處是,每個線程都對應一個本地變量的Map,所以一個線程可以存在多個線程本地變量。
get方法:
1 /** 2 * Returns the value in the current thread's copy of this 3 * thread-local variable. If the variable has no value for the 4 * current thread, it is first initialized to the value returned 5 * by an invocation of the {@link #initialValue} method. 6 * 7 * @return the current thread's value of this thread-local 8 */ 9 public T get() { 10 Thread t = Thread.currentThread(); 11 ThreadLocalMap map = getMap(t); 12 if (map != null) { 13 ThreadLocalMap.Entry e = map.getEntry(this); 14 if (e != null) 15 return (T)e.value; 16 } 17 // 如果值為空,則返回初始值 18 return setInitialValue(); 19 }
有了之前set方法的分析,get方法也同理,需要說明的是,如果沒有進行過set操作,那從ThreadLocalMap中拿到的值就是null,這時get方法會返回初始值,也就是調用initialValue()方法,ThreadLocal中這個方法默認返回null。當我們有需要第一次get時就能得到一個值時,可以繼承ThreadLocal,並且覆蓋initialValue()方法。
第3 部分 一個TheadLocal實例
1 /** 2 * 3 * @ClassName: SequenceNumber 4 * @author xingle 5 * @date 2015-3-9 上午9:54:23 6 */ 7 public class SequenceNumber { 8 //①通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值 9 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){ 10 public Integer initialValue(){ 11 return 0; 12 } 13 }; 14 15 //②獲取下一個序列值 16 public int getNextNum(){ 17 seqNum.set(seqNum.get()+1); 18 return seqNum.get(); 19 } 20 21 public static void main(String[] args){ 22 23 SequenceNumber sn = new SequenceNumber(); 24 //③ 3個線程共享sn,各自產生序列號 25 TestClient t1 = new TestClient(sn); 26 TestClient t2 = new TestClient(sn); 27 TestClient t3 = new TestClient(sn); 28 t1.start(); 29 t2.start(); 30 t3.start(); 31 } 32 33 private static class TestClient extends Thread{ 34 private SequenceNumber sn; 35 public TestClient(SequenceNumber sn){ 36 this.sn = sn; 37 } 38 39 public void run(){ 40 //④每個線程打出3個序列值 41 for (int i = 0 ;i<3;i++){ 42 System.out.println("thread["+Thread.currentThread().getName()+"] sn["+sn.getNextNum()+"]"); 43 } 44 } 45 } 46 47 }
通常我們通過匿名內部類的方式定義ThreadLocal的子類,提供初始的變量值,如①處所示。TestClient線程產生一組序列號,在③處,我們生成3個TestClient,它們共享同一個SequenceNumber實例。運行以上代碼,在控制台上輸出以下的結果:
每個線程所產生的序號雖然都共享同一個Sequence Number實例,但它們並沒有發生相互干擾的情況,而是各自產生獨立的序列號,這是因為我們通過ThreadLocal為每一個線程提供了單獨的副本。
2.Java線程(篇外篇):線程本地變量ThreadLocal
3.線程本地變更,即ThreadLocal-->Spring事務管理