Java線程本地存儲ThreadLocal

前言

  • ThreadLocal 是一種 無同步 的線程安全實現
  • 體現了 Thread-Specific Storage 模式:即便只有一個入口,內部也會爲每一個線程分配特有的存儲空間,線程間 沒有共享資源
  • 本文將總結 ThreadLocal 的用法與實現細節,但願能幫上忙

ThreadLocal 思惟導圖java

線程安全 示意圖android

1. 用法

ThreadLocal 的用法很簡單, ThreadLocal 提供了下列的public與protected方法,本文將引用 android.os.Looper.java 爲例子講解 ThreadLocal 的用法:編程

ThradlLocal UML類圖數組

// /frameworks/base/core/java/android/os/Looper.java

public class Looper { // ... // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } // 設置線程局部變量的值 sThreadLocal.set(new Looper(quitAllowed)); } public static Looper myLooper() { // 獲取線程局部變量的值 return sThreadLocal.get(); } public static void prepare() { prepare(true); } // ... }
  • ThreadLocal 爲 static final變量 ,泛型參數爲 Looper ,表示接受 Looper 類型的值
  • Looper#prepare() 中調用 ThreadLocal#set() 設置 變量的值,不一樣線程設置的值互不干擾,不會相互覆蓋
  • Looper#myLooper() 中調用 ThreadLocal#get() 獲取 變量的值,不一樣線程獲取的值互不干擾

Timethreads圖 - 01安全

  • 子類 重寫 ThreadLocal#initialValue() ,能夠設置變量的初始值,咱們查看相關源碼:數據結構

    public ConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); } public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS; // Find power-of-two sizes best matching arguments int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } this.segmentShift = 32 - sshift;//用於高位,判斷落在哪一個Segment this.segmentMask = ssize - 1;//用於取模。以前在hashmap的indexFor方法有提過。2的n次方-1 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = MIN_SEGMENT_TABLE_CAPACITY; while (cap < c) cap <<= 1; // create segments and segments[0] Segment<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]);//初始化第一個位置的Segment Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];//初始化Segments UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss; }
    • 在 ThreadLocal#get() 中嘗試獲取變量的值,若是爲空則會調用 ThreadLocal#setInitialValue() 設置設置初始值
    • 懶初始化 :直到第一次調用 get() 纔會設置初值
    • 默認 :設置初始值爲 null
  • ThreadLocal#remove() 用於 移除 變量以前存儲的值。若是在當前線程下次調用 ThreadLocal#get() 時,尚未 set() 新的值,依舊會使用 ThreadLocal#setInitialValue() 設置初始值。併發

到這裏咱們就能夠總結 ThreadLocal 的生命週期,以下圖所示:less

ThreadLocal生命週期 示意圖ssh

2. 編程規約

記得嗎?《阿里巴巴Java開發手冊》中提到過關於 ThreadLocal 的編程規約,以下所示:ide

  • 5.【強制】 SimpleDateFormate 是線程不安全的類,通常不要定義爲 static 變量,若是定義爲static,必須加鎖,或者使用 DateUtils 工具類

    正例:

    private static final ThreadLocal<DataFormat> df = new ThreadLocal<DateFormat>(){ @Override protected DateFormat initialValue(){ return new SimpleDateFormat("yyyy-MM-dd"); } };

    說明:若是是JDK8的應用,可使用 Instant 代替 Date , LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat ,官方給出的解釋:simple beautiful strong immutable thread-safe.

  • 15.【參考】(原文過於囉嗦,如下爲筆者轉述) ThreadLocal 變量建議使用 static 修飾,能夠保證變量在類初始化時建立,全部類實例能夠共享同一個靜態變量。

    注意到了嗎?在文章開頭的Looper.java源碼中, ThreadLocal 變量就是使用 static 修飾的

看到這裏,相信你已經掌握了 ThreadLocal 的用法,下一篇文章將深刻 ThreadLocal 的內部,探討數據結構的實現細節。

Segment

ConcurrentHashMap是由多個Segment組成的,Segment繼承了ReentrantLock,每次加鎖都是對某個Segment,不會影響其餘Segment,達到了鎖分離(也叫分段鎖)的做用。

每一個Segment又包含了HashEntry數組,HashEntry是一個鏈表。以下圖所示:

concurrencyLevel:併發數,默認16,直接影響segmentShift和segmentMask的值,以及Segment的初始化數量。Segment初始化的數量,爲最接近且大於的辦等於2的N次方的值,好比concurrencyLevel=16,Segment數量爲16,concurrencyLevel=17,Segment數量爲32。segmentShift的值是這樣的,好比Segment是32,相對於2的5次方,那麼他的值就是32-5,爲27,後面無符號右移27位,也就是取高5位的時候,就是0到31的值,此時Segment的下標也是0到31,取模後對應着每一個Segment。segmentMask就是2的n次方-1,這邊n是5,用於取模。以前在hashmap的indexFor方法有提過。

相關文章
相關標籤/搜索