手撕面試題ThreadLocal!!!

說明

面試官:講講你對ThreadLocal的一些理解。java

那麼咱們該怎麼回答呢????你也能夠思考下,下面看看零度的思考;面試

  • ThreadLocal用在什麼地方?安全

  • ThreadLocal一些細節!bash

  • ThreadLocal的最佳實踐!多線程

  • 思考併發

ThreadLocal用在什麼地方?

討論ThreadLocal用在什麼地方前,咱們先明確下,若是僅僅就一個線程,那麼都不用談ThreadLocal的,ThreadLocal是用在多線程的場景的!!!框架

ThreadLocal概括下來就2類用途:性能

  • 保存線程上下文信息,在任意須要的地方能夠獲取!!!
  • 線程安全的,避免某些狀況須要考慮線程安全必須同步帶來的性能損失!!!

保存線程上下文信息,在任意須要的地方能夠獲取!!!

因爲ThreadLocal的特性,同一線程在某地方進行設置,在隨後的任意地方均可以獲取到。從而能夠用來保存線程上下文信息。測試

經常使用的好比每一個請求怎麼把一串後續關聯起來,就能夠用ThreadLocal進行set,在後續的任意須要記錄日誌的方法裏面進行get獲取到請求id,從而把整個請求串起來。spa

還有好比Spring的事務管理,用ThreadLocal存儲Connection,從而各個DAO能夠獲取同一Connection,能夠進行事務回滾,提交等操做。

備註: ThreadLocal的這種用處,不少時候是用在一些優秀的框架裏面的,通常咱們不多接觸,反而下面的場景咱們接觸的更多一些!

線程安全的,避免某些狀況須要考慮線程安全必須同步帶來的性能損失!!!

ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。可是ThreadLocal也有侷限性,咱們來看看阿里規範:

每一個線程往ThreadLocal中讀寫數據是線程隔離,互相之間不會影響的,因此ThreadLocal沒法解決共享對象的更新問題!

因爲不須要共享信息,天然就不存在競爭問題了,從而保證了某些狀況下線程的安全,以及避免了某些狀況須要考慮線程安全必須同步帶來的性能損失!!!

這類場景阿里規範裏面也提到了:

ThreadLocal一些細節!

ThreaLocal使用示例代碼:

public class ThreadLocalTest {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {

        new Thread(() -> {
            try {
                for (int i = 0; i < 100; i++) {
                    threadLocal.set(i);
                    System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                threadLocal.remove();
            }
        }, "threadLocal1").start();


        new Thread(() -> {
            try {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                threadLocal.remove();
            }
        }, "threadLocal2").start();
    }
}
複製代碼

代碼截圖:

代碼運行結果:

從運行的結果咱們能夠看到threadLocal1進行set值對threadLocal2並無任何影響!

Thread、ThreadLocalMap、ThreadLocal總覽圖

Thread類有屬性變量threadLocals (類型是ThreadLocal.ThreadLocalMap),也就是說每一個線程有一個本身的ThreadLocalMap ,因此每一個線程往這個ThreadLocal中讀寫隔離的,而且是互相不會影響的。

一個ThreadLocal只能存儲一個Object對象,若是須要存儲多個Object對象那麼就須要多個ThreadLocal!!!

如圖:

看到上面的幾個圖,大概思路應該都清晰了,咱們Entry的key指向ThreadLocal用虛線表示弱引用 ,下面咱們來看看ThreadLocalMap:

java對象的引用包括 : 強引用,軟引用,弱引用,虛引用 。

由於這裏涉及到弱引用,簡單說明下:

弱引用也是用來描述非必需對象的,當JVM進行垃圾回收時,不管內存是否充足,該對象僅僅被弱引用關聯,那麼就會被回收。

當僅僅只有ThreadLocalMap中的Entry的key指向ThreadLocal的時候,ThreadLocal會進行回收的!!!

ThreadLocal被垃圾回收後,在ThreadLocalMap裏對應的Entry的鍵值會變成null,可是Entry是強引用,那麼Entry裏面存儲的Object,並無辦法進行回收,因此ThreadLocalMap 作了一些額外的回收工做。

雖然作了可是也會存在內存泄漏風險(我沒有遇到過,網上不少相似場景,因此會提到後面的ThreadLocal最佳實踐!!!

ThreadLocal的最佳實踐!

ThreadLocal被垃圾回收後,在ThreadLocalMap裏對應的Entry的鍵值會變成null,可是Entry是強引用,那麼Entry裏面存儲的Object,並無辦法進行回收,因此ThreadLocalMap 作了一些額外的回收工做。

備註: 不少時候,咱們都是用在線程池的場景,程序不中止,線程基本不會銷燬!!!

因爲線程的生命週期很長,若是咱們往ThreadLocal裏面set了很大很大的Object對象,雖然set、get等等方法在特定的條件會調用進行額外的清理,可是ThreadLocal被垃圾回收後,在ThreadLocalMap裏對應的Entry的鍵值會變成null,可是後續在也沒有操做set、get等方法了。

因此最佳實踐,應該在咱們不使用的時候,主動調用remove方法進行清理。

這裏把ThreadLocal定義爲static還有一個好處就是,因爲ThreadLocal有強引用在,那麼在ThreadLocalMap裏對應的Entry的鍵會永遠存在,那麼執行remove的時候就能夠正確進行定位到而且刪除!!!

最佳實踐作法應該爲:

try {
    // 其它業務邏輯
} finally {
    threadLocal對象.remove();
}
複製代碼

思考

若是面試的時候,能夠把上面的內容均可以講到,我的以爲就很是好了,回答的就挺完美了。可是若是你能夠進行下面的回答,那麼就更完美了。

對於ThreadLocal,我在看Netty源碼的時候,還了解過FastThreadLocal,xxxxx一些列內容,那就是一個升級了。

在我本地進行測試,FastThreadLocal的吞吐量是jdkThreadLocal的3倍左右。

備註: 因爲FastThreadLocal內容也很是很是多,並且有不少技巧,因此準備後續專門在開一篇進行串起來!!!


若是讀完以爲有收穫的話,歡迎點贊、關注、加公衆號【匠心零度】,查閱更多精彩歷史!!!

image
)
相關文章
相關標籤/搜索