自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ThreadLocal原理詳解--面試粉碎機(jī)

開發(fā) 前端
ThreadLocal不是用于解決共享變量的問題的,也不是為了協(xié)調(diào)線程同步而存在,而是為了方便每個(gè)線程處理自己的狀態(tài)而引入的一個(gè)機(jī)制。

一種解決多線程環(huán)境下成員變量的問題的方案,但是與線程同步無(wú)關(guān),其思路是為每一個(gè)線程創(chuàng)建一個(gè)單獨(dú)的變量副本,從而每個(gè)線程都可以獨(dú)立地改變所擁有的變量副本,而不會(huì)影響其他線程所對(duì)應(yīng)的副本;

ThreadLocal不是用于解決共享變量的問題的,也不是為了協(xié)調(diào)線程同步而存在,而是為了方便每個(gè)線程處理自己的狀態(tài)而引入的一個(gè)機(jī)制;

1、threadlocal使用

void set(Object value)

設(shè)置當(dāng)前線程的線程局部變量的值

public Object get()

該方法返回當(dāng)前線程所對(duì)應(yīng)的線程局部變量

public void remove()

將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當(dāng)線程結(jié)束后,對(duì)應(yīng)該線程的局部變量將自動(dòng)被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度;

創(chuàng)建一個(gè)ThreadLocal對(duì)象

private ThreadLocal<Integer> localInt = new ThreadLocal<>();
public int setAndGet(){
localInt.set(8);
return localInt.get();
}

設(shè)置變量的值為8

ThreadLocal里設(shè)置的值,只有當(dāng)前線程自己看得見,這意味著你不可能通過其他線程為它初始化值。為了彌補(bǔ)這一點(diǎn),ThreadLocal提供了一個(gè)withInitial()方法統(tǒng)一初始化所有線程的ThreadLocal的值:

private ThreadLocal<Integer> localInt = ThreadLocal.withInitial(() -> 6);

上述代碼將ThreadLocal的初始值設(shè)置為6,這對(duì)全體線程都是可見的

2、ThreadLocal源碼分析

ThreadLocal類源碼

/**
* ThreadLocals依賴于附加到每個(gè)線程的每個(gè)線程線性探測(cè)哈希映射(thread.ThreadLocals和可繼承的ThreadLocal)。
* ThreadLocal對(duì)象充當(dāng)鍵,通過threadLocalHashCode進(jìn)行搜索。
* 這是一個(gè)自定義哈希代碼(僅在ThreadLocalMaps中有用),在相同線程使用連續(xù)構(gòu)造的ThreadLocal的常見情況下消除了沖突,
* 而在不常見的情況下保持良好的行為。
*/
private final int threadLocalHashCode = nextHashCode();
/**
* 要給出的下一個(gè)哈希代碼。原子更新。從零開始。
*/
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
* 連續(xù)生成的哈希碼之間的差異-將隱式順序線程本地ID轉(zhuǎn)換為兩個(gè)大小表的冪的近似最優(yōu)的乘法哈希值。
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* 返回下一個(gè)哈希代碼。
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 設(shè)置調(diào)整大小閾值,在最壞的情況下為 2/3 負(fù)載系數(shù)
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 根據(jù)傳入的下標(biāo),返回下一個(gè)下標(biāo) (環(huán)形: 0-1-...-(len-1)-len-0-1-...-len)
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* ThreadLocalMap 內(nèi)部類
*/
static class ThreadLocalMap {
private Entry[] table;//數(shù)據(jù)數(shù)組
private int size = 0;//數(shù)組大小
private int threshold; //閾值
private static final int INITIAL_CAPACITY = 16; //默認(rèn)大小
/*
* Entry 繼承WeakReference,并且用ThreadLocal作為key.
* 如果key為null(entry.get() == null),意味著key不再被引用,
* 因此這時(shí)候entry也可以從table中清除。
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; //存儲(chǔ)線程值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始化
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}

作為一個(gè)存儲(chǔ)數(shù)據(jù)的類,關(guān)鍵點(diǎn)就在get和set方法。

(1)set 方法

//set 方法
public void set(T value) {
//獲取當(dāng)前線程
Thread t = Thread.currentThread();
//實(shí)際存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)類型
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,沒有則創(chuàng)建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//getMap方法
ThreadLocalMap getMap(Thread t) {
//thred中維護(hù)了一個(gè)ThreadLocalMap
return t.threadLocals;
}
//createMap
void createMap(Thread t, T firstValue) {
//實(shí)例化一個(gè)新的ThreadLocalMap,并賦值給線程的成員變量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

  • 首先獲取當(dāng)前線程,并根據(jù)當(dāng)前線程獲取一個(gè)Map。
  • 如果獲取的Map不為空,則將參數(shù)設(shè)置到Map中(當(dāng)前ThreadLocal的引用作為key)。
  • (這里調(diào)用了ThreadLocalMap的set方法)**。
  • 如果Map為空,則給該線程創(chuàng)建 Map,并設(shè)置初始值。
  • (這里調(diào)用了ThreadLocalMap的構(gòu)造方法)**。
  • 構(gòu)造方法`ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)。

/*
* firstKey : 本ThreadLocal實(shí)例(this)
* firstValue : 要保存的線程本地變量
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化table
table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
//計(jì)算索引(重點(diǎn)代碼)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//設(shè)置值
table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
size = 1;
//設(shè)置閾值
setThreshold(INITIAL_CAPACITY);
}

構(gòu)造函數(shù)首先創(chuàng)建一個(gè)長(zhǎng)度為16的Entry數(shù)組,然后計(jì)算出firstKey對(duì)應(yīng)的索引,然后存儲(chǔ)到table中,并設(shè)置size和threshold。

ThreadLocalMap中的set方法

private void set(ThreadLocal<?> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
//計(jì)算索引(重點(diǎn)代碼,剛才分析過了)
int i = key.threadLocalHashCode & (len-1);
/**
* 使用線性探測(cè)法查找元素(重點(diǎn)代碼)
*/
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//ThreadLocal 對(duì)應(yīng)的 key 存在,直接覆蓋之前的值
if (k == key) {
e.value = value;
return;
}
// key為 null,但是值不為 null,說明之前的 ThreadLocal 對(duì)象已經(jīng)被回收了,
// 當(dāng)前數(shù)組中的 Entry 是一個(gè)陳舊(stale)的元素
if (k == null) {
//用新元素替換陳舊的元素,這個(gè)方法進(jìn)行了不少的垃圾清理動(dòng)作,防止內(nèi)存泄漏
replaceStaleEntry(key, value, i);
return;
}
}
//ThreadLocal對(duì)應(yīng)的key不存在并且沒有找到陳舊的元素,則在空元素的位置創(chuàng)建一個(gè)新的Entry。
tab[i] = new Entry(key, value);
int sz = ++size;
/**
* cleanSomeSlots用于清除那些e.get()==null的元素,
* 這種數(shù)據(jù)key關(guān)聯(lián)的對(duì)象已經(jīng)被回收,所以這個(gè)Entry(table[index])可以被置null。
* 如果沒有清除任何entry,并且當(dāng)前使用量達(dá)到了負(fù)載因子所定義(長(zhǎng)度的2/3),那么進(jìn)行 * rehash(執(zhí)行一次全表的掃描清理工作)
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
/**
* 獲取環(huán)形數(shù)組的下一個(gè)索引
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

  • 首先還是根據(jù)key計(jì)算出索引 i,然后查找i位置上的Entry。
  • 若是Entry已經(jīng)存在并且key等于傳入的key,那么這時(shí)候直接給這個(gè)Entry賦新的value值。
  • 若是Entry存在,但是key為null,則調(diào)用replaceStaleEntry來更換這個(gè)key為空的Entry。
  • 不斷循環(huán)檢測(cè),直到遇到為null的地方,這時(shí)候要是還沒在循環(huán)過程中return,那么就在這個(gè)null的位置新建一個(gè)Entry,并且插入,同時(shí)size增加1。
  • ThreadLocalMap使用線性探測(cè)法來解決哈希沖突的;該方法一次探測(cè)下一個(gè)地址,直到有空的地址后插入,若整個(gè)空間都找不到空余的地址,則產(chǎn)生溢出。

(2)get()方法

//ThreadLocal中g(shù)et方法
public T get() {
Thread t = Thread.currentThread();
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();
}
//ThreadLocalMap中g(shù)etEntry方法
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);
}

通過計(jì)算出索引直接從數(shù)組對(duì)應(yīng)位置讀取即可;

(3)ThreadLocal特性

  • ThreadLocal和Synchronized都是為了解決多線程中相同變量的訪問沖突問題;
  • Synchronized是通過線程等待,犧牲時(shí)間來解決訪問沖突
  • ThreadLocal是通過每個(gè)線程單獨(dú)一份存儲(chǔ)空間,犧牲空間來解決沖突,并且相比于Synchronized,ThreadLocal具有線程隔離的效果,只有在線程內(nèi)才能獲取到對(duì)應(yīng)的值,線程外則不能訪問到想要的值;
  • 正因?yàn)門hreadLocal的線程隔離特性,使他的應(yīng)用場(chǎng)景相對(duì)來說更為特殊一些。在android中Looper、ActivityThread以及AMS中都用到了ThreadLocal;
  • 當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時(shí)候,就可以考慮采用ThreadLocal;

3、ThreadLocal內(nèi)存泄漏

我們調(diào)用threadLocal的set,get方法時(shí),會(huì)判斷當(dāng)前的key是否為null,將Entry中的value賦值為null,但是這個(gè)釋放value還有其他條件限制,并不是一定會(huì)發(fā)生,當(dāng)系統(tǒng)內(nèi)存不足時(shí),由于Entry中的key繼承軟引用,回被垃圾回收器回收調(diào),這時(shí),Entry中的key為null,無(wú)法被線程訪問,但是value仍然占用一定的內(nèi)存空間,雖然在調(diào)用set,get方法時(shí)有可能進(jìn)行系統(tǒng)回收,仍然無(wú)法回收無(wú)用所有內(nèi)存。無(wú)法被訪問的vlaue就會(huì)導(dǎo)致內(nèi)存泄漏,怎么解決內(nèi)存泄漏呢,最好的方法就是當(dāng)我們使用完變量副本后及時(shí)調(diào)用remove方法,手動(dòng)進(jìn)行垃圾回收。

public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 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();//清除value
expungeStaleEntry(i);
return;
}
}
}

  • 當(dāng)線程發(fā)生內(nèi)存泄漏時(shí),線程與內(nèi)部的ThreadLocalMap之間存在著強(qiáng)引用,導(dǎo)致ThreadLocalMap無(wú)法被釋放,這時(shí)由于ThreadLocalMap中的Entry的key為弱引用,ThreadLocal容易被回收,導(dǎo)致key為null,當(dāng)調(diào)用remove方法時(shí),會(huì)清除key為null對(duì)應(yīng)的value。
  • 所以為了避免內(nèi)存泄漏的出現(xiàn),我們?cè)谑褂猛闠hreadLocal的set方法后,及時(shí)調(diào)用remove方法進(jìn)行內(nèi)存釋放。避免出現(xiàn)內(nèi)存泄漏。

圖片

責(zé)任編輯:姜華 來源: Android開發(fā)編程
相關(guān)推薦

2017-08-23 16:10:12

2019-04-02 09:57:29

阿里AI自然語(yǔ)言

2017-06-12 08:14:54

電商打印設(shè)備京東

2023-08-02 08:54:58

Java弱引用鏈表

2021-05-06 08:55:24

ThreadLocal多線程多線程并發(fā)安全

2015-09-09 08:45:49

JavaThreadLocal

2023-10-07 08:26:40

多線程數(shù)據(jù)傳遞數(shù)據(jù)共享

2020-10-14 10:25:20

深度學(xué)習(xí)機(jī)器學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)

2023-09-08 08:20:46

ThreadLoca多線程工具

2022-03-17 08:55:43

本地線程變量共享全局變量

2020-07-28 08:59:22

JavahreadLocal面試

2011-04-28 20:33:14

2024-08-13 15:07:20

2018-02-06 22:18:47

Java虛擬機(jī)面試

2020-04-01 14:24:06

機(jī)器學(xué)習(xí)人工智能AI

2024-10-28 08:15:32

2024-03-26 00:33:59

JVM內(nèi)存對(duì)象

2024-09-24 10:28:22

2022-10-25 10:20:31

線程變量原理

2024-11-11 10:40:19

Java變量副本
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)