多線程:ThreadLocal探究之旅

ThreadLocal概述

  1. ThreadLocal提供線程一個獨立的局部變量,解決了變量併發訪問的衝突問題。
  2. 解決了線程安全的問題
  3. ThreadLocal 對比給Thread上synchronized同步機制:前者空間換時間、後者時間換空間

Threadlocal主題思維結構圖

在這裏插入圖片描述
核心部分會單獨拿出來畫圖、分析!

Threadlocal使用


@RequiresApi(api = Build.VERSION_CODES.O)
public class ThreadLocalTest {
    static ThreadLocal<String> local = ThreadLocal.withInitial(() -> {
        return "默認初始值";
    });
    static ThreadLocal local1 = new ThreadLocal();


    public static void main(String[] args) {
        System.out.print("local = " + local.get()+'\n');
        local.set("this Local");
        local1.set("this Local1111");
//        local.remove();
        System.out.print("Local1 = "+local1.get() + "     set Local = " + local.get());
    }

}

Log:
在這裏插入圖片描述

核心函數(對外 public/protected)

initialValue

當前線程初始副本變量值,一般由子類繼承ThreadLocal重寫,return一個默認值

get:返回此線程局部變量在當前線程副本中的值

  public T get() {
        Thread t = Thread.currentThread();
        //1.根據當前線程調用getMap()獲取Thread內部的ThreadLocal.ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //2 map不爲null,從map中獲取當前ThreadLocal的存儲實體對象
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 3 map爲null,調用setInitialValue
        // 3.1 map不存在,此線程中沒用維護的ThreadLocalMap對象
        // 3.2 map存在,但是沒有與ThreadLocal關聯的ThreadLocalMap.Entry
        return setInitialValue();
    }

    private T setInitialValue() {
        //1. 獲取初始化線程變量副本
        // 子類重寫 initialValue
        T value = initialValue();
        //2.根據當前線程調用getMap()獲取Thread內部的ThreadLocal.ThreadLocalMap
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //2.1 存在存入map
        if (map != null)
            map.set(this, value);
        // 2.2 不存在創建map,並存入
        else
            createMap(t, value);
        return value;
    }

set

public void set(T value) {
        // 獲取當前線程
        Thread t = Thread.currentThread();
        //獲取當前線程的TreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
        // 調用ThreadLocalMap的內部方法set(set後面會ThreadLocalMap分析中講解)
            map.set(this, value);
        // 不存在創建map,並存入
        else
            createMap(t, value);
    }

// 創建與ThreadLocal關聯的ThreadLocalMap
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  • remove:移除當前前程的副本變量值

通過getMap獲取當前線程的ThreadLoaclMap,不爲null調用ThreadLoaclMap內部的remove方法

  public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

ThreadLocalMap

在這裏插入圖片描述

  • set
private void set(ThreadLocal<?> key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
			// 這裏是爲了解決Hash衝突問題
            for (Entry e = tab[i];//獲取Entry對象
                 e != null;// 不爲null
                 e = tab[i = nextIndex(i, len)]) {
                 //i = nextIndex(i, len) 得到一個key值的元素的位置
                ThreadLocal<?> k = e.get();
				// 如果是同一個對象,最新的value會覆蓋掉舊value
                if (k == key) {
                    e.value = value;
                    return;
                }
				// 如果key爲null,則替換它的位置
                if (k == null) {
                    // 替換掉舊值
                    replaceStaleEntry(key, value, i);
                    // prevIndex(int i, int len)在此方法中調用
                    return;
                }
                 // 否則往後一個位置找,直到找到空的位置
            }
			// 上面的for循環主要作用:出現哈希衝突時,1:看是否是同一個對象或者是是否可替換,
			//                                  2:否則往後移動一位,繼續判斷
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
  • remove
  /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 // 如果存在這個對象,刪除
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

總結

Thread內部結構:
在這裏插入圖片描述

組成

  1. 每個Thread內部有一個Map(ThreadLocalMap)
  2. Map裏存儲(Key)ThreadLocal,和(value)線程變量副本
  3. Thread內部的map由ThreadLocal維護,由ThreadLocal負責向map 設置/獲取線程變量值
  4. 形成線程隔離

Entry

 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

  1. 存儲的key(ThreadLocal),爲弱引用
  2. 設計弱引用將對象生命週期和生命週期解綁

問題----------->

  1. 設計好處?
    節省內存的使用,Thread銷燬,內部的map也隨之銷燬

  2. 和synchronized比較?
    synchronized: 時間換空間
    ThreadLocal:空間換時間

  3. ThreadLocal內存泄漏的真實原因?
    ThreadLocalMap的生命週期和Thread一樣長,如果沒有手動刪除key,就會造成內存泄漏。在使用完ThreadLocal,調用remove刪除Entry

  4. 爲什麼使用弱引用?
    在使用完ThreadLocal後忘記調用remove,使用弱引用多一層保障:弱引用的ThradLocal被回收時,對應的value在下一次ThrealLocal調用get、set、remove時,舊元素會被清除,從而避免內存泄漏

  5. hashCode衝突?

// 是一個提供原子操作的Integer類,通過線程安全的方式操作加減
private static AtomicInteger nextHashCode =
        new AtomicInteger();
// HASH_INCREMENT = 0x61c88647 (16進制)目的哈希碼均勻的分佈在2的n次方的entry數組裏減少hash衝突
 private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
// 減少hash衝突的次數
 key.threadLocalHashCode & (len - 1);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章