面試官:說說你對ThreadLocal的瞭解

通常有多個孩子的家庭,買玩具都得買多個。若是就買一個,嘿嘿就比較刺激了。這就是避免共享,給孩子每人一個玩具對應到咱們Java中也就是每一個線程都有本身的本地變量,我們本身玩本身的,避免爭搶,和諧相處使得線程安全。java

Java就是經過ThreadLocal來實現線程本地存儲的。面試

這思路也很清晰,就是每一個線程要有本身的本地變量唄,那就Thread裏面搞一個私有屬性唄ThreadLocal.ThreadLocalMap threadLocals = null; 就是以下圖所示的這個關係算法

ThreadLocal

簡單的應用以下spring

public class Demo {
		private static final ThreadLocal<Foo> fooLocal = new ThreadLocal<Foo>();
 
		public static Foo getFoo() {
			return fooLocal.get();
		}
 
		public static void setFoo(Foo foo) {
			fooLocal.set(foo);
		}
	}
複製代碼

再深刻了解一下內部狀況,ThreadLocalMapThreadLocal的內部靜態類,它雖然叫Map可是和java.util.Map沒有啥親戚關係,只是它實現的功能像Map數組

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16;
        private Entry[] table;
複製代碼

能夠看到ThreadLocalMap裏面有個Entry數組,只有數組沒有像HashMap那樣有鏈表,所以當hash衝突的以後,ThreadLocalMap採用線性探測的方式解決hash衝突。安全

線性探測,就是先根據初始keyhashcode值肯定元素在table數組中的位置,若是這個位置上已經有其餘key值的元素被佔用,則利用固定的算法尋找必定步長的下個位置,依次直至找到可以存放的位置。在ThreadLocalMap步長是1。bash

用這種方式解決hash衝突的效率很低,所以要注意ThreadLocal的數量分佈式

/**
         * Increment i modulo len. 
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
複製代碼

並且能夠看到這個EntryThreadLocal的弱引用做爲key。那爲何要搞成弱引用(只要發生了GC弱引用對象就會被回收)呢?post

首先ThreadLocal內部沒有存儲任何的值,它的做用只是當咱們的ThreadLocalMap的key,讓線程能夠拿到對應的value。當咱們不須要用這個key的時候咱們,咱們把fooLocal=null這樣強引用就沒了。假設Entry裏面也是強引用的話,那等於這個ThreadLocal實例還有個強引用在,那麼咱們想讓GC回收fooLocal就回收不了了。那可能有人想,你弄成弱引用不是很危險啊,萬一GC一下不是沒了?別怕只要fooLocal這個強引用在這個ThreadLocal實例就不會回收的。(關於強軟弱虛引用能夠看我以前的文章四種引用方式的區別)性能

所以弄成弱引用,主要是讓沒用的ThreadLocal得以GC清除。

這裏可能還有人問那key清除掉了,value咋辦,這個Entry還在的呀。是的,當在使用線程池的狀況下,因爲線程的生命週期很長,某些大對象的key被移除了以後,value一直存在的就可能會致使內存泄漏。

不過java考慮到這點了。當調用get()、set()方法時會去找到那個key被幹掉的entry而後幹掉它。而且提供了remove()方法。雖然get()、set()會清理keynull的Entry,可是不是每次調用就會清理的,只有當get時候直接hash沒中,或者set時候也是直接hash沒中,開始線性探測時候,碰到key爲null的纔會清理。

//get 方法
        private Entry getEntry(ThreadLocal<?> key) {
            int i = key.threadLocalHashCode & (table.length - 1);
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;                           //命中就直接返回
            else
                return getEntryAfterMiss(key, i, e); //直接沒命中
        }
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;
            while (e != null) { //開始探測了
                ThreadLocal<?> k = e.get();
                if (k == key)  //命中了就返回
                    return e;
                if (k == null)  //探測到key是null的就清理
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len); //不然繼續
                e = tab[i];
            }
            return null;
        }
        //set 方法
        private void set(ThreadLocal<?> key, Object value) {
            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)]) {
                ThreadLocal<?> k = e.get();
                if (k == key) {
                    e.value = value;    //若是已經有就替換原有的value
                    return;
                }
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
複製代碼

所以,當不須要threadlocal的時候仍是顯示調用remove()方法較好。

結語

線程本地存儲本質就是避免共享,在使用中注意內存泄露問題和hash碰撞問題便可。使用仍是很普遍的像spring中事務就用到threadlocal


若有錯誤歡迎指正!

我的公衆號:yes的練級攻略

有相關面試進階(分佈式、性能調優、經典書籍pdf)資料等待領取

相關文章
相關標籤/搜索