ThreadLocal快速瞭解一下

NO IMAGE

歡迎點贊閱讀,一同學習交流,有疑問請留言 。
GitHub上也有開源 JavaHouse 歡迎star

1 引入

在Java8裡面,ThreadLocal 是一個泛型類。這個類可以提供線程變量。每個線程都有自己的變量。這意味著什麼?每一個線程都有自己的資源,就像在現實生活中,每一個程序員都有自己的一個對象,不用去競爭,絕對的線程安全啊。那麼 ThreadLocal 究竟怎麼用呢?

ThreadLocal快速瞭解一下

2 類的說明

* 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).

這是 ThreadLocal 類上面的說明。大概意思是提供線程變量,通常是被 static fields 修飾。

3 創建他

創建 ThreadLocal 有兩種方法,一種通過原始的無參構造器, 另一種是使用Java8的 lamaba 表達式。

3.1 無參構造器

源碼

/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}

使用並初始化

private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue(){
return 1;
}
};

3.2 lamaba 表達式


/**
* Creates a thread local variable. The initial value of the variable is
* determined by invoking the {@code get} method on the {@code Supplier}.
*
* @param <S> the type of the thread local's value
* @param supplier the supplier to be used to determine the initial value
* @return a new thread local variable
* @throws NullPointerException if the specified supplier is null
* @since 1.8
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}

使用並初始化

private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> 1);

其實使用如果使用 IDEA 的話, 編譯器也會提示可以有 lamaba。 但是看看裡面的源碼還是挺有意思的。

4 getter() 方法

/**
* Returns the value in the current thread's copy of this
* thread-local variable.  If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}

這裡可以看到 Thread.currentThread,獲取了當前線程,還有 ThreadLocalMap 這個類,他是一個哈希結構(key-value)。getMap() 方法通過當前線程去獲取他。 然後從再通過 this 關鍵字作為key,得到相應的值value。當然如果為空,就會返回初始化的值。

5 setter() 方法

什麼情況下不會返回初始化的默認值呢,答案就是調用了setter() 方法。先看源碼

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value.  Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
*        this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

大概意思就是將當前線程作為key, 要設的值作為 value 放在 ThreadLocalMap 這個哈希結構中。看到這裡,就知道為什麼說 ThreadLocal 可以提供線程變量了。他講每個線程都分開存儲,每個線程都有自己的獨立資源,不存在資源共享的情況,所以線程安全。

6 內存洩漏

每個線程變量都放進一個 ThreadLocalMap 裡面,不會有內存問題嗎。我截取一部分源碼

static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}

這裡可以看到了 WeakReference 這個類,可以知道 ThreadLocalMap 類是一個弱引用,按道理來說,一般執行完線程,就會被虛擬機的垃圾回收機制回收了。但是,真的是這樣的嗎。如果是線程池環境裡面,線程一直存在,那麼 ThreadLocal 就會變成起強引用了,不能被回收了。所以有內存洩漏問題。

那現在就是 remove() 方法出場的時候了。按照字面意思,可以知道,該方法可以清掉線程變量資源,事實也是這樣。所以,在程序的最後,最好調用一下 emove() 方法,防止內存洩漏。

參考

《實戰Java高併發程序設計》
ThreadLocal 源碼代

關注微信公眾號,隨時移動端閱讀

ThreadLocal快速瞭解一下

相關文章

阿里巴巴業務平臺-招聘“前端、客戶端”開發,虛位以待

mobx源碼解讀(四):講講autorun和reaction

自定義View實現一個日期選擇器

堡壘機WebSSH進階之實時監控和強制下線