想要去好點的公司,想要去前景好的公司都對技術要求挺高的,面試時技術問也會相應的難些,就拿美團來說,它好像比較喜歡執行緒安全機制問題,之前就有小伙伴被問倒了!所以今天就詳細講一講ThreadLocal原理,
ThreadLocal
ThreadLocal是執行緒的內部存盤類,可以在指定執行緒記憶體儲資料,只有指定執行緒可以得到存盤資料,
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*/
每個執行緒都有一個ThreadLocalMap的實體物件,并且通過ThreadLocal管理ThreadLocalMap,
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
每個新執行緒都會實體化為一個ThreadLocalMap并且賦值給成員變數ThreadLocals,使用時若已經存在threadLocals則直接使用已經存在的物件,
應用場景
當某些資料是以執行緒為作用域并且不同執行緒有不同資料副本時,考慮ThreadLocal,
無狀態,副本變數獨立后不影響業務邏輯的高并發場景,
如果如果業務邏輯強依賴于副本變數,則不適合用ThreadLocal解決,
get()與set()
set()是呼叫ThreadLocalMap的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);
}
//getMap方法
ThreadLocalMap getMap(Thread t) {
//thred中維護了一個ThreadLocalMap
return t.threadLocals;
}
//createMap
void createMap(Thread t, T firstValue) {
//實體化一個新的ThreadLocalMap,并賦值給執行緒的成員變數threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap:
ThreadLocalMap為每個Thread都維護了一個陣列table,ThreadLocal確定了一個陣列下標,而這個下標是value存盤的對應位置,
[圖片上傳中…(image-cd716a-1587459684812-0)]
ThreadLocalMaps是延遲構造的,因此只有在至少要放置一個條目時才創建,
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);
}
ThreadLocalMap初始化時創建了默認長度是16的Entry陣列,通過hashCode與length位運算確定索引值i,
每個Thread都有一個ThreadLocalMap型別,相當于每個執行緒Thread都有一個Entry型的陣列table,而一切讀取程序都是通過操作這個陣列table進行的,
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;
//通過&運算計算索引
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果存在key則覆寫
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();
}
將threadLocalHashCode與長度進行位運算得到索引,
threadLocalHashCode的代碼如下:
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
由于是static變數,threadLocalHashCode在每次加載threadLocal類時會重新初始化,同時會自增一次,增加HASH_INCREMENT(斐波那契散列乘數,通過該數散列出來的結果會比較均勻),
static變數也稱作靜態變數,靜態變數和非靜態變數的區別是:靜態變數被所有的物件所共享,在記憶體中只有一個副本,它當且僅當在類初次加載時會被初始化,
而非靜態變數是物件所擁有的,在創建物件的時候被初始化,存在多個副本,各個物件擁有的副本互不影響,static成員變數的初始化順序按照定義的順序進行初始化,
對于一個ThreadLocal來講,他的索引值i是確定的,對于不同執行緒,同一個threadlocal對應的是不同table的同一下標,即是table[i],不同執行緒之間的table是相互獨立的,
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();
}
remove() 方法
/**
* 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();
expungeStaleEntry(i);
return;
}
}
}
執行緒隔離特性:
執行緒隔離特性,只有在執行緒內才能獲取到對應的值,執行緒外不能訪問,
(1)Synchronized是通過執行緒等待,犧牲時間來解決訪問沖突,
(2)ThreadLocal是通過每個執行緒單獨一份存盤空間,犧牲空間來解決沖突,
記憶體泄露問題:
存在記憶體泄露問題,每次使用完ThreadLocal,都呼叫它的remove()方法,清除資料,
Demo程式:
import java.util.concurrent.atomic.AtomicInteger;
/**
* <h3>Exper1</h3>
* <p>ThreadLocalId</p>
*
* @author : cxc
* @date : 2020-04-01 23:48
**/
public class ThreadLocalId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal <Integer> threadId =
new ThreadLocal<Integer>()
{
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void remove() {
threadId.remove();
}
}
/**
* <h3>Exper1</h3>
* <p></p>
*
* @author : cxc
* @date : 2020-04-02 00:07
**/
public class ThreadLocalMain {
private static void incrementSameThreadId(){
try{
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread()
+"_"+i+",threadId:"+
ThreadLocalId.get());
}
}finally {
ThreadLocalId.remove();
}
}
public static void main(String[] args) {
incrementSameThreadId();
new Thread(new Runnable() {
@Override
public void run() {
incrementSameThreadId();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
incrementSameThreadId();
}
}).start();
}
}
總結:
咱們玩歸玩,鬧歸鬧,別拿面試開玩笑,ThreadLocal的原理在面試中幾乎被問爛了,Thread的私有資料是存盤在ThreadLocalMap,通過ThreadLoacl進行管理,要了解ThreadLocal的原理,最好多閱讀幾遍原始碼,尤其是ThreadLocalMap的原始碼部分,大家面試前要把知識點記牢,要是需要更多的Java后端的面試資料或者執行緒方面學習資料的可以點擊這里,暗號:cszq,也可以關注+私信我,免費提供!


最后祝大家作業都能順順利利哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/83827.html
標籤:其他
上一篇:八種經典排序演算法總結
下一篇:java Lambda 運算式
