天天看點

#yyds幹貨盤點#netty系列之:可能有人聽過ThreadLocal,但一定沒人聽過ThreadLocal對象池簡介ThreadLocalThreadLocalMapRecycler總結

簡介

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吧。