並發編程 01—— ThreadLocal


Java並發編程實踐 目錄

並發編程 01—— ThreadLocal

並發編程 02—— ConcurrentHashMap

並發編程 03—— 阻塞隊列和生產者-消費者模式

並發編程 04—— 閉鎖CountDownLatch 與 柵欄CyclicBarrier

並發編程 05—— Callable和Future

並發編程 06—— CompletionService : Executor 和 BlockingQueue

並發編程 07—— 任務取消

並發編程 08—— 任務取消 之 中斷

並發編程 09—— 任務取消 之 停止基於線程的服務

並發編程 10—— 任務取消 之 關閉 ExecutorService

並發編程 11—— 任務取消 之 “毒丸”對象

並發編程 12—— 任務取消與關閉 之 shutdownNow 的局限性

並發編程 13—— 線程池的使用 之 配置ThreadPoolExecutor 和 飽和策略

並發編程 14—— 線程池 之 整體架構

並發編程 15—— 線程池 之 原理一

並發編程 16—— 線程池 之 原理二

並發編程 17—— Lock

並發編程 18—— 使用內置條件隊列實現簡單的有界緩存

並發編程 19—— 顯式的Conditon 對象

並發編程 20—— AbstractQueuedSynchronizer 深入分析

並發編程 21—— 原子變量和非阻塞同步機制

 

概述

第1 部分 ThreadLocal是什么 

第2 部分 ThreadLocal的接口方法 

第3 部分 一個TheadLocal實例 

參考

第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為每一個線程提供了單獨的副本。

 

 


參考:

1.ThreadLocal

2.Java線程(篇外篇):線程本地變量ThreadLocal

3.線程本地變更,即ThreadLocal-->Spring事務管理

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM