1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        ThreadLocal 的開發(fā)應(yīng)用

        共 5351字,需瀏覽 11分鐘

         ·

        2021-05-16 15:15

        作者:KerryWu

        來源:SegmentFault 思否社區(qū)


        ThreadLocal是線程私有的局部變量存儲容器,可以理解成每個線程都有自己專屬的存儲容器,用來存儲線程私有變量。ThreadLocal 在日常開發(fā)框架中應(yīng)用廣泛,但用不好也會出現(xiàn)各種問題,本文就此講解一下。

        1. 應(yīng)用場景

        ThreadLocal 的常見應(yīng)用場景有兩種:

        1. 多線程并發(fā)場景中,用來保障線程安全。
        2. 處理較為復(fù)雜的業(yè)務(wù)時,使用ThreadLocal代替參數(shù)的顯示傳遞。

        1.1. 保障線程安全

        多線程訪問同一個共享變量的時候容易出現(xiàn)并發(fā)問題,特別是多個線程對一個變量進行寫入的時候,為了保證線程安全,一般使用者在訪問共享變量的時候需要進行額外的同步措施才能保證線程安全性,如:synchronized、Lock之類的鎖。
        ThreadLocal是除了加鎖這種同步方式之外的一種,規(guī)避多線程訪問出現(xiàn)線程不安全的方法。當我們在創(chuàng)建一個變量后,如果每個線程對其進行訪問的時候訪問的都是線程自己的變量,這樣就不會存在線程不安全問題。
        ThreadLocal是JDK包提供的,它提供線程本地變量,如果創(chuàng)建一個ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內(nèi)存中的變量,從而規(guī)避了線程安全問題。

        1.2. 顯示傳遞參數(shù)

        這里舉幾個例子:
        示例1:獲取接口的當前請求用戶
        在后臺接口業(yè)務(wù)邏輯的全過程中,如果需要在多個地方獲取當前請求用戶的信息。通常的一種做法就是:在接口請求時,通過過濾器、攔截器、AOP等方式,從session或token中獲取當前用戶信息,存入ThreadLocal中。
        在整個接口處理過程中,如果沒有另外創(chuàng)建線程,都可以直接從ThreadLocal變量中獲取當前用戶,而無需再從Session、token中驗證和獲取用戶。這種方案設(shè)計不僅提高性能,最重要的是將原本復(fù)雜的邏輯和代碼實現(xiàn),變得簡潔明了。例如下面的這個例子:
        (1)定義ThreadLocal變量:UserProfileThread.java
        public class UserProfileThread {
            private static ThreadLocal<UserProfile> USER_PROFILE_TL =new ThreadLocal<>();

            public static void  setUserProfile(UserProfile userProfile){
                USER_PROFILE_TL.set(userProfile);
            }

            public static UserProfile getUserProfile() {
                return USER_PROFILE_TL.get();
            }

            public static String getCurrentUser() {
                return Optional.ofNullable(USER_PROFILE_TL.get())
                        .map(UserProfile::getUid)
                        .orElse(UserProfile.ANONYMOUS_USER);
            }
        }
        (2)在過濾器中設(shè)置變量值:
        @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                UserProfile userProfile = null;
                // ... 驗證和獲取用戶信息 userProfile
                UserProfileThread.setUserProfile(userProfile);
                filterChain.doFilter(servletRequest, servletResponse);
            }
        (3)獲取當前用戶信息
        //獲取當前用戶
        String uid=UserProfileThread.getCurrentUser();
        //獲取當前用戶對象
        UserProfile user=UserProfileThread.getUserProfile();
        示例2:spring框架中保證數(shù)據(jù)庫事務(wù)在同一個連接下執(zhí)行
        要想實現(xiàn)jdbc事務(wù), 就必須是在同一個連接對象中操作,多個連接下事務(wù)就會不可控,需要借助分布式事務(wù)完成。那spring框架如何保證數(shù)據(jù)庫事務(wù)在同一個連接下執(zhí)行的呢?
        DataSourceTransactionManager 是spring的數(shù)據(jù)源事務(wù)管理器,它會在你調(diào)用getConnection()的時候從數(shù)據(jù)庫連接池中獲取一個connection, 然后將其與ThreadLocal綁定,事務(wù)完成后解除綁定。這樣就保證了事務(wù)在同一連接下完成。

        2. 實現(xiàn)原理

        ThreadLocal類提供set/get方法存儲和獲取value值,但實際上ThreadLocal類并不存儲value值,真正存儲是靠ThreadLocalMap這個類。
        每個線程實例都對應(yīng)一個TheadLocalMap實例,我們可以在同一個線程里實例化很多個ThreadLocal來存儲很多種類型的值,這些ThreadLocal實例分別作為key,對應(yīng)各自的value,最終存儲在Entry table數(shù)組中。
        我們看看ThreadLocal的set方法:
        public class ThreadLocal<T> {
         public void set(T value) {
                Thread t = Thread.currentThread();
                ThreadLocalMap map = getMap(t);
                if (map != null)
                    map.set(this, value);
                else
                    createMap(t, value);
            }

            ThreadLocalMap getMap(Thread t) {
                return t.threadLocals;
            }

            void createMap(Thread t, T firstValue) {
                t.threadLocals = new ThreadLocalMap(this, firstValue);
            }
            // 省略其他方法
        }
        set的邏輯比較簡單,就是獲取當前線程的ThreadLocalMap,然后往map里添加KV,K是當前ThreadLocal實例,V是我們傳入的value。這里需要注意一下,map的獲取是需要從Thread類對象里面取,看一下Thread類的定義。
        public class Thread implements Runnable {
            ThreadLocal.ThreadLocalMap threadLocals = null;
            //省略其他
        }
        Thread類維護了一個ThreadLocalMap的變量引用。
        因此,我們可以得出如下結(jié)論:
        • 每個線程是一個Thread實例,其內(nèi)部維護一個threadLocals的實例成員,其類型是ThreadLocal.ThreadLocalMap。
        • ThreadLocal本身并不是一個容器,我們存取的value實際上存儲在ThreadLocalMap中,ThreadLocal只是作為TheadLocalMap的key。

        3. 注意事項

        ThreadLocal實例有提供remove()方法,用于回收對象,清除對應(yīng)的內(nèi)存占用。這個方法通常容易被忽略,而導(dǎo)致出現(xiàn)了各種問題。如下面幾種:
        1. 線程復(fù)用:在“獲取接口的當前請求用戶”的例子中,Tomcat中是通過線程池來處理用戶請求的,而線程池中線程是復(fù)用的。肯定會出現(xiàn)一個線程前后被不同用戶的接口請求復(fù)用的情況,因此需要對用過的ThreaLocal變量進行覆蓋或清除。
        2. 內(nèi)存溢出:由于ThreadLocalMap的生命周期跟Thread一樣長,如果創(chuàng)建的ThreadLocal變量很多,即對應(yīng)的key占用的內(nèi)存很大,但卻沒有手動刪除,到了一定程度就會導(dǎo)致內(nèi)存泄漏。


        點擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動和交流,掃描下方”二維碼“或在“公眾號后臺回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

        - END -


        瀏覽 17
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            亚洲精品suv视频 | 国产又粗又大又爽视频 | ssni—392侵犯新任女教师 | 五月天无码在线 | 午夜av电影网 | 91真实高潮激烈痉挛颤抖 | 奶水尤物又软又喷奶水h | youjizz中国丰满少妇 | 国产高潮女人叫床视频片 | 精品无人区一区二区三区88AV |