成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

ThreadLocal就是這么簡單

zhouzhou / 2314人閱讀

摘要:簡要言之往中填充的變量屬于當前線程,該變量對其他線程而言是隔離的。在辦理時又告訴機構(gòu)來,把我的身份證房產(chǎn)證學生證通通給他。

前言

今天要研究的是ThreadLocal,這個我在一年前學習JavaWeb基礎(chǔ)的時候接觸過一次,當時在baidu搜出來的第一篇博文ThreadLocal,在評論下很多開發(fā)者認為那博主理解錯誤,給出了很多有關(guān)的鏈接來指正(可原博主可能沒上博客了,一直沒做修改)。我也去學習了一番,可惜的是當時還沒有記錄的習慣,直到現(xiàn)在僅僅記住了一些當時學過的皮毛。

因此,做一些技術(shù)的記錄是很重要的~同時,ThreadLocal也是面試非常常見的面試題,對Java開發(fā)者而言也是一個必要掌握的知識點~

當然了,如果我有寫錯的地方請大家多多包涵,歡迎在評論下留言指正~

一、什么是ThreadLocal
聲明:本文使用的是JDK 1.8

首先我們來看一下JDK的文檔介紹:

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

For example, the class below generates unique identifiers local to each * thread. * A thread"s id is assigned the first time it invokes {@code ThreadId.get()} * and remains unchanged on subsequent calls. */

結(jié)合我的總結(jié)可以這樣理解:ThreadLocal提供了線程的局部變量,每個線程都可以通過set()get()來對這個局部變量進行操作,但不會和其他線程的局部變量進行沖突,實現(xiàn)了線程的數(shù)據(jù)隔離~。

簡要言之:往ThreadLocal中填充的變量屬于當前線程,該變量對其他線程而言是隔離的。

二、為什么要學習ThreadLocal?

從上面可以得出:ThreadLocal可以讓我們擁有當前線程的變量,那這個作用有什么用呢???

2.1管理Connection

最典型的是管理數(shù)據(jù)庫的Connection:當時在學JDBC的時候,為了方便操作寫了一個簡單數(shù)據(jù)庫連接池,需要數(shù)據(jù)庫連接池的理由也很簡單,頻繁創(chuàng)建和關(guān)閉Connection是一件非常耗費資源的操作,因此需要創(chuàng)建數(shù)據(jù)庫連接池~

那么,數(shù)據(jù)庫連接池的連接怎么管理呢??我們交由ThreadLocal來進行管理。為什么交給它來管理呢??ThreadLocal能夠?qū)崿F(xiàn)當前線程的操作都是用同一個Connection,保證了事務(wù)!

當時候?qū)懙拇a:

public class DBUtil {
    //數(shù)據(jù)庫連接池
    private static BasicDataSource source;

    //為不同的線程管理連接
    private static ThreadLocal local;


    static {
        try {
            //加載配置文件
            Properties properties = new Properties();

            //獲取讀取流
            InputStream stream = DBUtil.class.getClassLoader().getResourceAsStream("連接池/config.properties");

            //從配置文件中讀取數(shù)據(jù)
            properties.load(stream);

            //關(guān)閉流
            stream.close();

            //初始化連接池
            source = new BasicDataSource();

            //設(shè)置驅(qū)動
            source.setDriverClassName(properties.getProperty("driver"));

            //設(shè)置url
            source.setUrl(properties.getProperty("url"));

            //設(shè)置用戶名
            source.setUsername(properties.getProperty("user"));

            //設(shè)置密碼
            source.setPassword(properties.getProperty("pwd"));

            //設(shè)置初始連接數(shù)量
            source.setInitialSize(Integer.parseInt(properties.getProperty("initsize")));

            //設(shè)置最大的連接數(shù)量
            source.setMaxActive(Integer.parseInt(properties.getProperty("maxactive")));

            //設(shè)置最長的等待時間
            source.setMaxWait(Integer.parseInt(properties.getProperty("maxwait")));

            //設(shè)置最小空閑數(shù)
            source.setMinIdle(Integer.parseInt(properties.getProperty("minidle")));

            //初始化線程本地
            local = new ThreadLocal<>();


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection() throws SQLException {
        if(local.get()!=null){
            return local.get();
        }else{
        
          //獲取Connection對象
          Connection connection = source.getConnection();

          //把Connection放進ThreadLocal里面
          local.set(connection);

          //返回Connection對象
          return connection;
       }
    }

    //關(guān)閉數(shù)據(jù)庫連接
    public static void closeConnection() {
        //從線程中拿到Connection對象
        Connection connection = local.get();

        try {
            if (connection != null) {
                //恢復(fù)連接為自動提交
                connection.setAutoCommit(true);

                //這里不是真的把連接關(guān)了,只是將該連接歸還給連接池
                connection.close();

                //既然連接已經(jīng)歸還給連接池了,ThreadLocal保存的Connction對象也已經(jīng)沒用了
                local.remove();

            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


}

同樣的,Hibernate對Connection的管理也是采用了相同的手法(使用ThreadLocal,當然了Hibernate的實現(xiàn)是更強大的)~

2.2避免一些參數(shù)傳遞

避免一些參數(shù)的傳遞的理解可以參考一下Cookie和Session:

每當我訪問一個頁面的時候,瀏覽器都會幫我們從硬盤中找到對應(yīng)的Cookie發(fā)送過去。

瀏覽器是十分聰明的,不會發(fā)送別的網(wǎng)站的Cookie過去,只帶當前網(wǎng)站發(fā)布過來的Cookie過去

瀏覽器就相當于我們的ThreadLocal,它僅僅會發(fā)送我們當前瀏覽器存在的Cookie(ThreadLocal的局部變量),不同的瀏覽器對Cookie是隔離的(Chrome,Opera,IE的Cookie是隔離的【在Chrome登陸了,在IE你也得重新登陸】),同樣地:線程之間ThreadLocal變量也是隔離的....

那上面避免了參數(shù)的傳遞了嗎??其實是避免了。Cookie并不是我們手動傳遞過去的,并不需要寫來進行傳遞參數(shù)...

在編寫程序中也是一樣的:日常中我們要去辦理業(yè)務(wù)可能會有很多地方用到身份證,各類證件,每次我們都要掏出來很麻煩

    // 咨詢時要用身份證,學生證,房產(chǎn)證等等....
    public void consult(IdCard idCard,StudentCard studentCard,HourseCard hourseCard){

    }

    // 辦理時還要用身份證,學生證,房產(chǎn)證等等....
    public void manage(IdCard idCard,StudentCard studentCard,HourseCard hourseCard) {

    }

    //......

而如果用了ThreadLocal的話,ThreadLocal就相當于一個機構(gòu),ThreadLocal機構(gòu)做了記錄你有那么多張證件。用到的時候就不用自己掏了,問機構(gòu)拿就可以了。

在咨詢時的時候就告訴機構(gòu):來,把我的身份證、房產(chǎn)證、學生證通通給他。在辦理時又告訴機構(gòu):來,把我的身份證、房產(chǎn)證、學生證通通給他。...

    // 咨詢時要用身份證,學生證,房產(chǎn)證等等....
    public void consult(){

        threadLocal.get();
    }

    // 辦理時還要用身份證,學生證,房產(chǎn)證等等....
    public void takePlane() {
        threadLocal.get();
    }

這樣是不是比自己掏方便多了。

當然了,ThreadLocal可能還會有其他更好的作用,如果知道的同學可在評論留言哦~~~

三、ThreadLocal實現(xiàn)的原理

想要更好地去理解ThreadLocal,那就得翻翻它是怎么實現(xiàn)的了~~~

聲明:本文使用的是JDK 1.8

首先,我們來看一下ThreadLocal的set()方法,因為我們一般使用都是new完對象,就往里邊set對象了

    public void set(T value) {

        // 得到當前線程對象
        Thread t = Thread.currentThread();
        
        // 這里獲取ThreadLocalMap
        ThreadLocalMap map = getMap(t);

        // 如果map存在,則將當前對象threadLocal作為key,要存儲的對象作為value存到map里面去
        if (map != null)
            map.set(this, value);
        else
            // 如果map不存在,則在當前線程中創(chuàng)建出一個ThreadLocalMap!
            createMap(t, value);
    }

上面有個ThreadLocalMap,我們?nèi)タ纯催@是什么?

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        //....很長
}

通過上面我們可以發(fā)現(xiàn)的是ThreadLocalMap是ThreadLocal的一個內(nèi)部類。用Entry類來進行存儲

我們的值都是存儲到這個Map上的,key是當前ThreadLocal對象

如果該Map不存在,則初始化一個:

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

如果該Map存在,則從Thread中獲取!

    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

Thread維護了ThreadLocalMap變量

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null

從上面又可以看出,ThreadLocalMap是在ThreadLocal中使用內(nèi)部類來編寫的,但對象的引用是在Thread中!

于是我們可以總結(jié)出:Thread為每個線程維護了ThreadLocalMap這么一個Map,而ThreadLocalMap的key是ThreadLocal對象本身,value則是要存儲的對象

有了上面的基礎(chǔ),我們看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();
    }
3.1ThreadLocal原理總結(jié)

每個Thread維護著一個ThreadLocalMap的引用

ThreadLocalMap是ThreadLocal的內(nèi)部類,用Entry來進行存儲

調(diào)用ThreadLocal的set()方法時,實際上就是往ThreadLocalMap設(shè)置值,key是ThreadLocal對象,值是傳遞進來的對象

調(diào)用ThreadLocal的get()方法時,實際上就是往ThreadLocalMap獲取值,key是ThreadLocal對象

ThreadLocal本身并不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value。

正因為這個原理,所以ThreadLocal能夠?qū)崿F(xiàn)“數(shù)據(jù)隔離”,獲取當前線程的局部變量值,不受其他線程影響~

四、避免內(nèi)存泄露

我們來看一下ThreadLocal的對象關(guān)系引用圖:

ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應(yīng)key就會導致內(nèi)存泄漏,而不是因為弱引用。

想要避免內(nèi)存泄露就要手動remove()掉!

五、總結(jié)

ThreadLocal這方面的博文真的是數(shù)不勝數(shù),隨便一搜就很多很多~站在前人的肩膀上總結(jié)了這篇博文~

最后要記住的是:ThreadLocal設(shè)計的目的就是為了能夠在當前線程中有屬于自己的變量,并不是為了解決并發(fā)或者共享變量的問題

如果看得不夠過癮,覺得不夠深入的同學可參考下面的鏈接,很多的博主還開展了一些擴展知識,我就不一一展開了~

參考博文:

http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/

https://www.cnblogs.com/zhangjk1993/archive/2017/03/29/6641745.html#_label2

http://www.cnblogs.com/dolphin0520/p/3920407.html

http://www.cnblogs.com/dolphin0520/p/3920407.html

http://www.iteye.com/topic/103804

https://www.cnblogs.com/xzwblog/p/7227509.html

https://blog.csdn.net/u012834750/article/details/71646700

https://blog.csdn.net/winwill2012/article/details/71625570

https://juejin.im/post/5a64a581f265da3e3b7aa02d

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術(shù)文章,想要獲取更多的Java資源的同學,可以關(guān)注微信公眾號:Java3y

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68903.html

相關(guān)文章

  • 【源起Netty 外傳】FastThreadLocal怎么Fast?

    摘要:實現(xiàn)原理淺談幫助理解的示意圖中有一屬性,類型是的靜態(tài)內(nèi)部類。剛剛說過,是一個中的靜態(tài)內(nèi)部類,則是的內(nèi)部節(jié)點。這個會在線程中,作為其屬性初始是一個數(shù)組的索引,達成與類似的效果。的方法被調(diào)用時,會根據(jù)記錄的槽位信息進行大掃除。 概述 FastThreadLocal的類名本身就充滿了對ThreadLocal的挑釁,快男FastThreadLocal是怎么快的?源碼中類注釋坦白如下: /** ...

    gxyz 評論0 收藏0
  • python學習筆記 - ThreadLocal

    摘要:為了避免改亂為,我們在前面已經(jīng)提到說要加鎖。僅供一個線程使用,線程間相互不影響。例如下列程序中函數(shù)中定義的變量就是局部變量。所有綁定的參數(shù)都是線程隔離的。下面展示一下代碼創(chuàng)建一個全局的對象初始化一個線程內(nèi)變量,該變量線程間互不影響。 我們在編寫多線程程序的時候,往往會遇到兩種類型的變量。 一種是全局變量,多個線程共享。為了避免改亂為,我們在前面已經(jīng)提到說要加鎖。 一種是局部變量。僅供...

    Berwin 評論0 收藏0
  • (八)ThreadLocal的使用及原理分析

    摘要:什么是,簡單翻譯過來就是本地線程,但是直接這么翻譯很難理解的作用,如果換一種說法,可以稱為線程本地存儲。魔數(shù)的選取和斐波那契散列有關(guān),對應(yīng)的十進制為。而斐波那契散列的乘數(shù)可以用如果把這個值給轉(zhuǎn)為帶符號的,則會得到。 什么是ThreadLocal ThreadLocal,簡單翻譯過來就是本地線程,但是直接這么翻譯很難理解ThreadLocal的作用,如果換一種說法,可以稱為線程本地存儲。...

    asoren 評論0 收藏0
  • Java并發(fā)編程之原子性操作

    摘要:將與當前線程建立一對一關(guān)系的值移除。為了讓方法里的操作具有原子性,也就是在一個線程執(zhí)行這一系列操作的同時禁止其他線程執(zhí)行這些操作,提出了鎖的概念。 上頭一直在說以線程為基礎(chǔ)的并發(fā)編程的好處了,什么提高處理器利用率啦,簡化編程模型啦。但是磚家們還是認為并發(fā)編程是程序開發(fā)中最不可捉摸、最詭異、最扯犢子、最麻煩、最惡心、最心煩、最容易出錯、最不符合社會主義核心價值觀的一個部分~ 造成這么多最...

    instein 評論0 收藏0
  • Java線程有哪些不太為人所知的技巧與用法?

    摘要:從本文中你將會看到線程提供的一些不太常用的方法及技術(shù)。最大的好處就是你所設(shè)定的優(yōu)先級能在每個線程獲得的時間上有所體現(xiàn),不過完全依賴于線程優(yōu)先級的做法是不推薦的。這省掉了正常關(guān)閉線程的那些麻煩事,可以立即將線程結(jié)束掉。 轉(zhuǎn)載出處:Java線程的5個使用技巧英文原文鏈接 蘿卜白菜各有所愛。像我就喜歡Java。學無止境,這也是我喜歡它的一個原因。日常工作中你所用到的工具,通常都有些你從來沒...

    freewolf 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<