[轉(zhuǎn)]ThreadLocal-如何解決哈希沖突

第一贰健、前言

ThreadLocal使用的是自定義的ThreadLocalMap乳附,接下來我們來探究一下ThreadLocalMap的hash沖突解決方式。

ThreadLocal通過線性探測(cè)法/開放地址法來解決hash沖突逻悠,
HashMap則通過鏈地址法來解決hash沖突

第二志笼、ThreadLocal的set()方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocal.ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
ThreadLocal.ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}

1.代碼很簡(jiǎn)單,獲取當(dāng)前線程,并獲取當(dāng)前線程的ThreadLocalMap實(shí)例(從getMap(Thread t)中很容易看出來)。
2.如果獲取到的map實(shí)例不為空,調(diào)用map.set()方法,否則調(diào)用構(gòu)造函數(shù)ThreadLocal.ThreadLocalMap(this, firstValue)實(shí)例化map痊土∫拊可以看出來線程中的ThreadLocalMap使用的是延遲初始化,在第一次調(diào)用get()或者set()方法的時(shí)候才會(huì)進(jìn)行初始化。

第三赁酝、構(gòu)造函數(shù)ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //初始化table
    table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
    //計(jì)算索引
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //設(shè)置值
    table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
    size = 1;
    //設(shè)置閾值
    setThreshold(INITIAL_CAPACITY);
}

主要說一下計(jì)算索引犯祠,firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)。
關(guān)于& (INITIAL_CAPACITY - 1),這是取模的一種方式酌呆,對(duì)于2的冪作為模數(shù)取模衡载,用此代替%(2^n),這也就是為啥容量必須為2的冥隙袁,在這個(gè)地方也得到了解答痰娱。

關(guān)于firstKey.threadLocalHashCode:

private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
private static AtomicInteger nextHashCode =  new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

這里定義了一個(gè)AtomicInteger類型弃榨,每次獲取當(dāng)前值并加上HASH_INCREMENT,HASH_INCREMENT = 0x61c88647,這個(gè)值和斐波那契散列有關(guān)(這是一種乘數(shù)散列法猜揪,只不過這個(gè)乘數(shù)比較特殊惭墓,是32位整型上限2^32-1乘以黃金分割比例0.618…的值2654435769,用有符號(hào)整型表示就是-1640531527而姐,去掉符號(hào)后16進(jìn)制表示為0x61c88647),其主要目的就是為了讓哈希碼能均勻的分布在2的n次方的數(shù)組里, 也就是Entry[] table中划咐,這樣做可以盡量避免hash沖突拴念。

第四、ThreadLocalMap中的set()

ThreadLocalMap使用閉散列:(開放地址法或者也叫線性探測(cè)法)解決哈希沖突褐缠。
線性探測(cè)法的地址增量di = 1, 2, … 其中政鼠,i為探測(cè)次數(shù)。該方法一次探測(cè)下一個(gè)地址队魏,直到有空的地址后插入公般,若整個(gè)空間都找不到空余的地址,則產(chǎn)生溢出胡桨。
假設(shè)當(dāng)前table長(zhǎng)度為16官帘,也就是說如果計(jì)算出來key的hash值為14,如果table[14]上已經(jīng)有值昧谊,并且其key與當(dāng)前key不一致刽虹,那么就發(fā)生了hash沖突,這個(gè)時(shí)候?qū)?4加1得到15呢诬,取table[15]進(jìn)行判斷涌哲,這個(gè)時(shí)候如果還是沖突會(huì)回到0,取table[0],以此類推尚镰,直到可以插入阀圾。
按照上面的描述,可以把table看成一個(gè)環(huán)形數(shù)組狗唉。
先看一下線性探測(cè)相關(guān)的代碼初烘,從中也可以看出來table實(shí)際是一個(gè)環(huán):

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
    return ((i - 1 >= 0) ? i - 1 : len - 1);
}
private void set(ThreadLocal<?> key, Object value) {
    ThreadLocal.ThreadLocalMap.Entry[] tab = table;
    int len = tab.length;
    //計(jì)算索引,上面已經(jīng)有說過敞曹。
    int i = key.threadLocalHashCode & (len-1);
    /**根據(jù)獲取到的索引進(jìn)行循環(huán)账月,如果當(dāng)前索引上的table[i]不為空,在沒有return的情況下澳迫,
    * 就使用nextIndex()獲取下一個(gè)(上面提到到線性探測(cè)法)局齿。*/
    for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null;
        e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //table[i]上key不為空,并且和當(dāng)前key相同橄登,更新value
        if (k == key) {
            e.value = value;
            return;
        }
        /**table[i]上的key為空抓歼,說明被回收了
         * 說明改table[i]可以重新使用讥此,用新的key-value將其替換,并刪除其他無效的entry*/
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //不存在也沒有舊元素就創(chuàng)建一個(gè)
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();//擴(kuò)容
}

第五、閉散列

當(dāng)我們要往哈希表中插入一個(gè)數(shù)據(jù)時(shí)谣妻,通過哈希函數(shù)計(jì)算該值的哈希地址萄喳,當(dāng)我們找到哈希地址時(shí)卻發(fā)現(xiàn)該位置已經(jīng)被別的數(shù)據(jù)插入了,那么此時(shí)我們就找緊跟著這一位置的下一個(gè)位置蹋半,看是否能夠插入他巨,如果能則插入,不能則繼續(xù)探測(cè)緊跟著當(dāng)前位置的下一個(gè)位置减江。

假設(shè)要將key=y的元素存入哈希表染突,通過哈希函數(shù)求出哈希地址為7,比較哈希地址7的元素的key是否等于y辈灼,不相等份企,繼續(xù)比對(duì)哈希地址為8的元素…直到找到哈希地址為2的位置,可以存儲(chǔ)巡莹。

轉(zhuǎn)自 https://blog.csdn.net/fengyuyeguirenenen/article/details/124897002

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末司志,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子降宅,更是在濱河造成了極大的恐慌骂远,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钉鸯,死亡現(xiàn)場(chǎng)離奇詭異吧史,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)唠雕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門贸营,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人岩睁,你說我怎么就攤上這事钞脂。” “怎么了捕儒?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵冰啃,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我刘莹,道長(zhǎng)阎毅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任点弯,我火速辦了婚禮扇调,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抢肛。我一直安慰自己狼钮,他們只是感情好碳柱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著熬芜,像睡著了一般莲镣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涎拉,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天瑞侮,我揣著相機(jī)與錄音,去河邊找鬼曼库。 笑死区岗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毁枯。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼叮称,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼种玛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瓤檐,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤赂韵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后挠蛉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祭示,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年谴古,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了质涛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掰担,死狀恐怖汇陆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情带饱,我是刑警寧澤毡代,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站勺疼,受9級(jí)特大地震影響教寂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜执庐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一酪耕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧耕肩,春花似錦因妇、人聲如沸问潭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狡忙。三九已至,卻和暖如春址芯,著一層夾襖步出監(jiān)牢的瞬間灾茁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工谷炸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留北专,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓旬陡,卻偏偏與公主長(zhǎng)得像拓颓,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子描孟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 一驶睦、簡(jiǎn)述 通過構(gòu)造性能良好的哈希函數(shù),可以減少?zèng)_突匿醒,但一般不可能完全避免沖突场航,因此解決沖突是哈希法的另一個(gè)關(guān)鍵問題...
    Djbfifjd閱讀 1,647評(píng)論 0 3
  • 一、簡(jiǎn)介 ?ThreadLocal 不知道大家有沒有用過廉羔,但至少聽說過溉痢,這篇文章主要講解下ThreadLocal的...
    SunnyMore閱讀 1,498評(píng)論 0 3
  • 一. 簡(jiǎn)介 提醒篇幅較大需耐心。 簡(jiǎn)介來自ThreadLocal類注釋 ThreadLocal類提供了線程局部 (...
    BrightLoong閱讀 9,155評(píng)論 2 14
  • ThreadLocal( 線程局部變量 ) 在線程之間共享變量是存在風(fēng)險(xiǎn)的憋他,有時(shí)可能要避免共享變量孩饼,使用 Thre...
    真的有神閱讀 182評(píng)論 0 0
  • [toc]實(shí)際上,在分析整個(gè)Reference包源碼之前举瑰,重點(diǎn)關(guān)注的問題就是ThreadLocal的源碼捣辆。這也是學(xué)...
    冬天里的懶喵閱讀 337評(píng)論 0 0