簡介
JDK中的Thread大家肯定用過,只要是用過非同步程式設計的同學肯定都熟悉。為了儲存Thread中特有的變數,JDK引入了ThreadLocal類來專門對Thread的本地變數進行管理。
ThreadLocal
很多新人可能不明白ThreadLocal到底是什麼,它和Thread到底有什麼關係。
其實很簡單,ThreadLocal本質上是一個key,它的value就是Thread中一個map中儲存的值。
每個Thread中都有一個Map, 這個Map的型別是ThreadLocal.ThreadLocalMap。我們先不具體討論這個ThreadLocalMap到底是怎麼實現的。現在就簡單將其看做是一個map即可。
接下來,我們看下一個ThreadLocal的工作流程。
首先來看一下ThreadLocal的使用例子:
public class ThreadId {
// 一個執行緒ID的自增器
private static final AtomicInteger nextId = new AtomicInteger(0);
// 為每個Thread分配一個執行緒
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// 返回當前執行緒的ID
public static int get() {
return threadId.get();
}
}
上面的類是做什麼用的呢?
當你在不同的執行緒環境中呼叫ThreadId的get方法時候,會返回不同的int值。所以可以看做是ThreadId為每個執行緒生成了一個執行緒ID。
我們來看下它是怎麼工作的。
首先我們呼叫了ThreadLocal<Integer>的get方法。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();
}
get方法中,我們第一步獲取當前的執行緒Thread,然後getMap返回當前Thread中的ThreadLocalMap物件。
如果Map不為空,則取出以當前ThreadLocal為key對應的值。
如果Map為空,則呼叫初始化方法:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
初始化方法首先判斷當前Thread中的ThreadLocalMap是否為空,如果不為空則設定初始化的值。
如果為空則建立新的Map:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
大家可以看到整個ThreadLocalMap中的Key就是ThreadLocal本身,而Value就是ThreadLocal中定義的泛型的值。
現在我們來總結一下ThreadLocal到底是做什麼的。
每個Thread中都有一個ThreadLocal.ThreadLocalMap的Map物件,我們希望向這個Map中存放一些特定的值,通過一個特定的物件來訪問到存放在Thread中的這個值,這樣的物件就是ThreadLocal。
通過ThreadLocal的get方法,就可以返回繫結到不同Thread物件中的值。
ThreadLocalMap
上面我們簡單的將ThreadLocalMap看做是一個map。事實上ThreadLocalMap是一個物件,它裡面存放的每個值都是一個Entry.
這個Entry不同於Map中的Entry,它是一個static的內部類:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
注意,這裡的Entry繼承自WeakReference,表示這個Entry的key是弱引用物件,如果key沒有強引用的情況下,會在gc中被回收。從而保證了Map中資料的有效性。
ThreadLocalMap中的值都存放在Entry陣列中:
private Entry[] table;
我們看一下怎麼從ThreadLocalMap中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);
}
key.threadLocalHashCode 可以簡單的看做是ThreadLocal代表的key的值。
而 key.threadLocalHashCode & (table.length - 1) 則使用來計算當前key在table中的index。
這裡使用的是位運算,用來提升計算速度。實際上這個計算等同於:
key.threadLocalHashCode % table.length
是一個取模運算。
如果按照取模運算的index去查詢,找到就直接返回。
如果沒找到則會遍歷呼叫nextIndex方法,修改index的值,只到查詢完畢為止:
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;
}
Recycler
ThreadLocal本質上是將ThreadLocal這個物件和不同的Thread進行繫結,通過ThreadLocal的get方法可以獲得儲存在不同Thread中的值。
歸根結底,ThreadLocal和Thread是一對多的關係。因為ThreadLocal在ThreadLocalMap中是弱引用,所以當ThreadLocal被置為空之後,對應的ThreadLocalMap中的物件會在下一個垃圾回收過程中被回收,從而為Thread中的ThreadLocalMap節省一個空間。
那麼當我們的Thread是一個長時間執行的Thread的時候,如果在這個Thread中分配了很多生命週期很短的物件,那麼會生成很多待回收的垃圾物件,給垃圾回收器造成壓力。
為了解決這個問題,netty為我們提供了Recycler類,用來回收這些短生命週期的物件。接下來,我們來探究一下Recycler到底是怎麼工作的。
在這之前,我們先看下怎麼使用Recycler。
public class MyObject {
private static final Recycler<MyObject> RECYCLER = new Recycler<MyObject>() {
protected MyObject newObject(Recycler.Handle<MyObject> handle) {
return new MyObject(handle);
}
}
public static MyObject newInstance(int a, String b) {
MyObject obj = RECYCLER.get();
obj.myFieldA = a;
obj.myFieldB = b;
return obj;
}
private final Recycler.Handle<MyObject> handle;
private int myFieldA;
private String myFieldB;
private MyObject(Handle<MyObject> handle) {
this.handle = handle;
}
public boolean recycle() {
myFieldA = 0;
myFieldB = null;
return handle.recycle(this);
}
}
MyObject obj = MyObject.newInstance(42, "foo");
...
obj.recycle();
本質上,Recycler就像是一個工廠類,通過它的get方法來生成對應的類物件。當這個物件需要被回收的時候,呼叫Recycler.Handle中的recycle方法,即可將物件回收。
先看一下生成物件的get方法:
public final T get() {
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
Stack<T> stack = threadLocal.get();
DefaultHandle<T> handle = stack.pop();
if (handle == null) {
handle = stack.newHandle();
handle.value = newObject(handle);
}
return (T) handle.value;
}
上面程式碼的含義就是,先判斷是否超過了單個執行緒允許的最大容量,如果是,則返回一個新的物件,繫結一個空的handler,表示這個新建立的物件是不可以被回收的。
如果不是,則從threadLocal中拿到當前執行緒繫結的Stack。然後從Stack中取出最上面的元素,如果Stack中沒有物件,則建立新的物件,並繫結handle。
最後返回handle繫結的物件。
再看一下handle的回收物件方法recycle:
public void recycle(Object object) {
if (object != value) {
throw new IllegalArgumentException("object does not belong to handle");
}
Stack<?> stack = this.stack;
if (lastRecycledId != recycleId || stack == null) {
throw new IllegalStateException("recycled already");
}
stack.push(this);
}
上面的程式碼先去判斷和handle繫結的物件是不是要回收的物件。只有相等的時候才進行回收。
而回收的本質就是將物件push到stack中,供後續get的時候取出使用。
所以,Recycler能夠節約垃圾回收物件個數的原因是,它會將不再使用的物件儲存到Stack中,並在下次get的時候返回,重複使用。這也就是我們在回收需要重置物件屬性的原因:
public boolean recycle() {
myFieldA = 0;
myFieldB = null;
return handle.recycle(this);
}
總結
如果你在一個執行緒中會有多個同型別的短生命週期物件,那麼不妨試試Recycle吧。
本文已收錄於 http://www.flydean.com/47-netty-thread-…al-object-pool-2/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!