c语言中0xde 这怎么用,为什么使用0x61c88647
在Java1.4之前,ThreadLocals會導致線程之間發生競爭。在新的設計里,每一個線程都有他們自己的ThreadLocalMap,用來提高吞吐量,然而,我們仍然面臨內存泄漏的可能性,因為長時間運行線程的ThreadLocalMap中的值不會被清除
在Java的早期版本中,ThreadLocals在多個線程進行訪問的時候存在競爭問題,使得它們在多核應用程序中幾乎無用。在Java 1.4中,引入了一個新的設計,設計者把ThreadLocals直接存儲在Thread中。當我們現在調用ThreadLocal的get方法時,將會返回一個當前線程里的實例ThreadLocalMap(ThreadLocal的一個內部類)
當一個線程退出時,它會刪除它ThreadLocal里的所有值。這發生在exit()方法中,垃圾回收之前,如果我們在使用ThreadLocal后忘記調用remove()方法,那么當線程退出后值還會存在。
ThreadLocalMap包含了對ThreadLocal的弱引用以及值的強引用,但是,它并不會判斷ReferenceQueue里面哪些弱引用的值已經被清除,因為Entry不可能立即從ThreadLocalMap中清除。
從線程Thread的角度來看,每個線程內部都會持有一個對ThreadLocalMap實例的引用,ThreadLocalMap實例相當于線程的局部變量空間,存儲著線程各自的數據,具體如下:
Entry
Entry繼承自WeakReference類,是存儲線程私有變量的數據結構。ThreadLocal實例作為引用,意味著如果ThreadLocal實例為null,就可以從table中刪除對應的Entry。
class Entry extends WeakReference>{
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
復制代碼
ThreadLocalMap
內部使用table數組存儲Entry,默認大小INITIAL_CAPACITY(16),先介紹幾個參數:
size:table中元素的數量。
threshold:table大小的2/3,當size >= threshold時,遍歷table并刪除key為null的元素,
如果刪除后size >= threshold*3/4時,需要對table進行擴容。
ThreadLocal.set() 實現
public void set(T value){
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t){
return t.threadLocals;
}
復制代碼
從上面代碼中看出來:
從當前線程Thread中獲取ThreadLocalMap實例。
ThreadLocal實例和value封裝成Entry。
接下去看看Entry存入table數組如何實現的:
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;
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();
}
復制代碼
1.通過ThreadLocal的nextHashCode方法生成hash值。
private static AtomicInteger nextHashCode = new AtomicInteger();
private static int nextHashCode(){
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
復制代碼
從nextHashCode方法可以看出,ThreadLocal每實例化一次,其hash值就原子增加HASH_INCREMENT。
2.通過 hash & (len -1) 定位到table的位置i,假設table中i位置的元素為f。
3.如果f != null,假設f中的引用為k:
如果k和當前ThreadLocal實例一致,則修改value值,返回。
如果k為null,說明這個f已經是stale(陳舊的)的元素。調用replaceStaleEntry方法刪除table中所有陳舊的元素(即entry的引用為null)并插入新元素,返回。
否則通過nextIndex方法找到下一個元素f,繼續進行步驟3。
如果f == null,則把Entry加入到table的i位置中。
通過cleanSomeSlots刪除陳舊的元素,如果table中沒有元素刪除,需判斷當前情況下是否要進行擴容。
4.如果f == null,則把Entry加入到table的i位置中。
5.通過cleanSomeSlots刪除陳舊的元素,如果table中沒有元素刪除,需判斷當前情況下是否要進行擴容。
table擴容
如果table中的元素數量達到閾值threshold的3/4,會進行擴容操作,過程很簡單:
private void resize(){
//舊數組
Entry[] oldTab = table;
//舊數組長度
int oldLen = oldTab.length;
//新數組長度 = 舊數組長度*2
int newLen = oldLen * 2;
//新數組
Entry[] newTab = new Entry[newLen];
//計數
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal> k = e.get();
if (k == null) {
e.value = null; // Help the GC
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
復制代碼
1.新建新的數組newTab,大小為原來的2倍。
2.復制table的元素到newTab,忽略陳舊的元素,假設table中的元素e需要復制到newTab的i位置,如果i位置存在元素,則找下一個空位置進行插入。
ThreadLocal.get() 實現
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();
}
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);
}
復制代碼
獲取當前的線程的threadLocals。
如果threadLocals不為null,則通過ThreadLocalMap.getEntry方法找到對應的entry,如果其引用和當前key一致,則直接返回,否則在table剩下的元素中繼續匹配。
如果threadLocals為null,則通過setInitialValue方法初始化,并返回。
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)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
復制代碼
魔數0x61c88647
生成hash code間隙為這個魔數,可以讓生成出來的值或者說ThreadLocal的ID較為均勻地分布在2的冪大小的數組中。
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode(){
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
復制代碼可以看出,它是在上一個被構造出的ThreadLocal的ID/threadLocalHashCode的基礎上加上一個魔數0x61c88647的。
這個魔數的選取與斐波那契散列有關,0x61c88647對應的十進制為1640531527。
斐波那契散列的乘數可以用(long) ((1L << 31) * (Math.sqrt(5) - 1))可以得到2654435769,如果把這個值給轉為帶符號的int,則會得到-1640531527。換句話說
(1L << 32) - (long) ((1L << 31) * (Math.sqrt(5) - 1))得到的結果就是1640531527也就是0x61c88647
。
通過理論與實踐,當我們用0x61c88647作為魔數累加為每個ThreadLocal分配各自的ID也就是threadLocalHashCode再與2的冪取模,得到的結果分布很均勻。
ThreadLocalMap使用的是線性探測法,均勻分布的好處在于很快就能探測到下一個臨近的可用slot,從而保證效率。。為了優化效率。
ThreadLocal與內存泄漏
之所以有關于內存泄露的討論是因為在有線程復用如線程池的場景中,一個線程的壽命很長,大對象長期不被回收影響系統運行效率與安全。如果線程不會復用,用完即銷毀了也不會有ThreadLocal引發內存泄露的問題
當我們仔細讀過ThreadLocalMap的源碼,我們可以推斷,如果在使用的ThreadLocal的過程中,顯式地進行remove是個很好的編碼習慣,這樣是不會引起內存泄漏。
如果您必須使用ThreadLocal,請確保在您完成該操作后立即刪除該值,并且最好在將線程返回到線程池之前。 最佳做法是使用remove()而不是set(null),因為這將導致WeakReference立即被刪除,并與值一起被刪除。
總結
以上是生活随笔為你收集整理的c语言中0xde 这怎么用,为什么使用0x61c88647的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言接受socket发送尾部有乱码,C
- 下一篇: 指针冒泡排序c语言代码,用指针编写冒泡排