ThreadLocal源碼認識和理解

ThreadLocal是一個非常常用對象,線程的變量副本,每個線程隔離,但對具體如何實現線程隔離,這篇文章試着去了解一下。

hreadLocal代碼演示

public class ThreadLocalDemo {

    private static ExecutorService executor = new ThreadPoolExecutor(5, 5,
            10L, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>());

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();


    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            int j = i;
            executor.execute(() -> {
                String value = "" + j + j + j;
                threadLocal.set(value);
                System.out.println(Thread.currentThread().getName() + "設置值:" + value);
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String getValue = threadLocal.get();
                System.out.println(Thread.currentThread().getName() + "獲取:" + getValue);

            });
        }


    }
}

在這裏插入圖片描述

不同線程設置的值和獲取的值是同一個,實現線程之間設置的隔離。

ThreadLocal的數據結構

對於一個ThreadLocal 而言
在這裏插入圖片描述
Thread類有一個類型爲ThreadLocal.ThreadLocalMap的成員變量inheritableThreadLocals
在這裏插入圖片描述
如果是多個ThreadLocal的展示情況:
在這裏插入圖片描述

public class ThreadLocalDemo {



    public static void main(String[] args) throws Exception {
        ThreadLocal<String> local1 = new ThreadLocal<>();
        ThreadLocal<String> local2 = new ThreadLocal<>();
        ThreadLocal<String> local3 = new ThreadLocal<>();
        ThreadLocal<String> local4 = new ThreadLocal<>();

        local1.set("local1");
        local2.set("local2");
        local3.set("local3");
        local4.set("local4");
        //得到當前線程
        Thread t = Thread.currentThread();
        Class<? extends Thread> clz = t.getClass();
        //獲取當前線程threadLocals class也就是  ThreadLocal.ThreadLocalMap
        Field field = clz.getDeclaredField("threadLocals");
        field.setAccessible(true);
        //得到 ThreadLocal.ThreadLocalMap具體實例
        Object threadLocalMap = field.get(t);
        Class<?> tlmClass = threadLocalMap.getClass();
        //獲取到 ThreadLocal.ThreadLocalMap 中的數組 class
        Field tableField = tlmClass.getDeclaredField("table");
        tableField.setAccessible(true);
        //獲取到 ThreadLocal.ThreadLocalMap 中的數組 中實例對象
        Object[] arr = (Object[]) tableField.get(threadLocalMap);
        for (Object o : arr) {
            if (o != null) {
                Class<?> entryClass = o.getClass();
                Field valueField = entryClass.getDeclaredField("value");
                // 繼承關係  由static class Entry extends WeakReference<ThreadLocal<?>>
                // 可得 entryClass.getSuperclass() 獲取WeakReference 的class
                //繼承關係 public class WeakReference<T> extends Reference<T>
                //可得 entryClass.getSuperclass().getSuperclass() 得到 Reference的繼承關係
                //referent 是 Reference 的成員變量,也是threadLocal
                Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
                valueField.setAccessible(true);
                referenceField.setAccessible(true);
                System.out.println(String.format("key:%s,值:%s", referenceField.get(o), valueField.get(o)));
            }
        }


    }
}

在這裏插入圖片描述
從圖片和代碼中可以同一個線程的不同ThreadLocal和值放到同一個線程中成員變量threadLocals中

上面的例子雖然定義ThreadLocal,可能會讓人聯想到數據都是存在ThreadLocal這個類中,
但實際上Thread類有一個類型爲ThreadLocal.ThreadLocalMap的成員變量threadLocals,也就是說每個線程有一個自己的ThreadLocalMap。

ThreadLocal.ThreadLocalMap 將threadLocal 作爲key進行存儲,value爲我們設置的值。對於同一個ThreadLocal來說,不同線程將值存儲到不同線程內部的實例變量中,就對於單個threadLocal 就不存在線程安全的問題。

ThreadLocal的set 方法

// threadLocal 的get方法
 public void set(T value) {
        Thread t = Thread.currentThread();
        //ThreadLocalMap  是取至thread的成員變量
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        //從之前圖片上Thread 中threadLocals的成員變量,和這可以看出中ThreadLocalMap  是懶加載
            createMap(t, value);
    }

// threadLocal  中的getMap方法可以看出,ThreadLocalMap  是取至thread的成員變量
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

//這個是ThreadLocalMap 中set的方法
private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            //算出ThreadLocalMap  中數組的下標
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 //計算第一次下標爲空,則向後找下一個下標
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                //清空槽位上key爲null的值
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
               //調用cleanSomeSlots()做一次啓發式清理工作,清理散列數組中Entry的key過期的數據,
               //如果無數據清理,且數據量超過超過了閾值(數組長度的2/3),進行rehash()操作
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
         //擴容
                rehash();
        }
// nextIndex一直向後,直到entry不爲空
private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

//
 private void rehash() {
 			//清理數據
            expungeStaleEntries();

            // Use lower threshold for doubling to avoid hysteresis
            //清理完如果容量超過3/4則擴容
            if (size >= threshold - threshold / 4)
                resize();
        }
        //從傳入節點清理數據,如果有數據清理則爲true ,無數據清理則爲false
 private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

ThreadLocal的get方法

public T get() {
        Thread t = Thread.currentThread();
        //和set方法一樣,去Thread 獲取 thread的成員變量
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    //通過threadLocal 獲取節點
  private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            //如果直接是該threadLocal 直接返回
            if (e != null && e.get() == key)
                return e;
            else
            //1、該節點上面值爲null  2、該節點和其他節點hash衝突了
                return getEntryAfterMiss(key, i, e);
        }


 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
			//e 如果直接爲null 不進入while循環,直接返回 null
            while (e != null) {
                ThreadLocal<?> k = e.get();
                //遇到該hash的值,直接返回
                if (k == key)
                    return e;
                    //如果過期,清理過期值
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

ThreadLocal中ThreadLocalMap 的 Entry extends WeakReference

//ThreadLocalMap 的 Entry extends WeakReference<ThreadLocal<?>> 中key的值是弱引用
   static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

但value是set的值屬於強引用,當threadlocal 爲gc回收之後,value一直留在ThreadLocalMap 之中。造成內存泄漏,從源碼上分析,get,set,擴容 的時候都有檢查是否ThreadLocalMap 中是否key過期,如果過期將value設置爲空。我們在使用當中,如果該value中值不再使用可以使用remove方法置value置空。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章