深入探討java.lang.ThreadLocal類

深入探討java.lang.ThreadLocal類

一摊求、概述

ThreadLocal是什么呢校辩?其實ThreadLocal并非是一個線程的本地實現(xiàn)版本斋陪,它并不是一個Thread癌椿,而是一個ThreadLocalVariable(線程局部變量)躺坟。

變量值的共享可以使用 public static 變量的形式属桦,所有的線程都使用同一個 public static 變量. 如果想實現(xiàn)每一個每一個線程都有自己的共享變量該如何解決呢? JDK中提供的類ThreadLocal正是為了解決這樣的問題.
ThreadLocal主要解決的就是每個線程綁定自己的值,可以將 ThreadLocal類比喻成全局存放數(shù)據(jù)的盒子,盒子中可以存儲每個線程的私有數(shù)據(jù).

從線程角度看熊痴,只要線程是活動的并且 ThreadLocal實例時可訪問的,那么每個線程都保持一個對其線程共享的私有變量副本的隱式引用聂宾,在線程消失之后果善,其線程的所有私有變量副本都會被垃圾回收(除非存在對這些變量副本的其他引用)。

通過ThreadLocal存取的數(shù)據(jù)系谐,總是與當(dāng)前線程相關(guān)巾陕,也就是說,JVM為每個運行的線程,綁定了私有的本地實例存取空間惜论,從而為多線程環(huán)境常出現(xiàn)的并發(fā)訪問問題提供了一種隔離機制许赃。

ThreadLocal 是如何做到為每一個線程維護私有變量副本的呢?其實實現(xiàn)的思路很簡單馆类,在以前混聊,底層實現(xiàn)是一個HashMap,key是當(dāng)前線程乾巧,value是該實例句喜。但是現(xiàn)在的設(shè)計思路改了!沟于!現(xiàn)在的底層實現(xiàn)是Thread個HashMap咳胃,每個HashMap的key是這個ThreadLocal實例,value是那個對象的副本旷太。
ThreadLocal在1.6版本后是在Thread類中有一個ThreadLocalMap的變量展懈,然后用Thread.currentThread().threadLocals.get(this)來引用的各線程變量副本.

為什么這樣搞呢?如果是原來的設(shè)計方案供璧,那么在大型項目里有很多Thread和很多ThreadLocal的前提下存崖,就會有ThreadLocal個HashMap,每個里面就有Thread個元素睡毒。在Thread很多的情況下性能會低来惧。

還有一點,當(dāng)一個線程停止時演顾,對應(yīng)的ThreadLocal副本都不存在了供搀,可以銷毀一個HashMap。但用第一種設(shè)計思路的話這些HashMap都在钠至。

概括起來說葛虐,對于多線程資源共享問題,同步機制采用了“以時間換空間”的方式棉钧,而ThreadLocal采用了“以空間換時間”的方式挡闰。前者僅提供一份變量,在不同線程排隊訪問掰盘,而后者為每一個線程都提供了一份變量,因此可以同時訪問而不互相影響赞季。

二愧捕、API說明

ThreadLocal() -->創(chuàng)建一個線程本地變量


get() -->返回線程本地變量的當(dāng)前線程副本中的值,如果第一次調(diào)用get()方法則返回的值是null


protected T initialValue() --> 返回此線程本地變量的當(dāng)前線程的初始值。最多在每次訪問線程來獲得每個線程局部變量時調(diào)用此方法一次申钩,即線程第一次使用 get() 方法訪問變量的時候次绘。如果線程先于 get 方法調(diào)用 set(T) 方法,則不會在線程中再調(diào)用 initialValue 方法


set(T value) -->將線程本地變量的當(dāng)前線程副本中的值設(shè)置為指定值.許多應(yīng)用程序不需要此方法,它們只依賴于initialValue()方法來設(shè)置線程局部變量的值.


void remove() --> 移除此線程局部變量的值,這可能有助于減少線程局部變量的存儲需求邮偎。如果再次訪問此線程局部變量管跺,那么在默認情況下它將擁有其 initialValue。

在程序中一般都重寫initialValue方法禾进,以給定一個特定的初始值豁跑。

三、代碼實例

3.1 方法get()與null
public class ThreadLocalTest {
    public static ThreadLocal t1 = new ThreadLocal();

    public static void main(String[] args) {
        if(t1.get() == null) {
            System.out.println("從未放過值");
            t1.set("我的值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());

    }
}
運行結(jié)果:
從未放過值
我的值
我的值

從上面的運行結(jié)果來看,第一次調(diào)用t1對象的get()方法時返回的值是null,通過調(diào)用set()方法賦值后順利取出值并打印到控制臺上.說明不同線程中的值是可以放入ThreadLocal類中進行保存的;

3.2 Hibernate的Session 工具類HibernateUtil

這個類是Hibernate官方文檔中HibernateUtil類,用于Session管理;

public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定義SessionFactory
 
    static {
        try {
            // 通過默認配置文件hibernate.cfg.xml創(chuàng)建SessionFactory
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            log.error("初始化SessionFactory失斝涸啤艇拍!", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    //創(chuàng)建線程局部變量session,用來保存Hibernate的Session
    public static final ThreadLocal session = new ThreadLocal();
 
    /**
     * 獲取當(dāng)前線程中的Session
     * @return Session
     * @throws HibernateException
     */
    public static Session currentSession() throws HibernateException {
        Session s = (Session) session.get();
        // 如果Session還沒有打開宠纯,則新開一個Session
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);         //將新開的Session保存到線程局部變量中
        }
        return s;
    }
 
    public static void closeSession() throws HibernateException {
        //獲取線程局部變量卸夕,并強制轉(zhuǎn)換為Session類型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

在這個類中,由于沒有重寫ThreadLocal的initialValue()方法婆瓜,則首次創(chuàng)建線程局部變量session其初始值為null快集,第一次調(diào)用currentSession()的時候,線程局部變量的get()方法也為null廉白。因此个初,對session做了判斷,如果為null蒙秒,則新開一個Session勃黍,并保存到線程局部變量session中,這一步非常的關(guān)鍵晕讲,這也是public static final ThreadLocal session = new ThreadLocal()所創(chuàng)建的對象session通過get()獲取的對象能強制轉(zhuǎn)換為Hibernate Session對象的原因覆获。

3.3 驗證線程變量的隔離性
public class Tools {
    public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
    @Override
    public  void run() {
        try{
            for (int i = 0; i < 20; i++) {
                Tools.t1.set("ThreadA" + (i+1));
                Thread.sleep(200);
                System.out.println("ThreadA get Value = " +Tools.t1.get());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class ThreadB extends Thread {
    @Override
    public void run() {
        try{
            for (int i = 0; i < 20; i++) {
                Tools.t1.set("ThreadB" + (i+1));
                Thread.sleep(200);
                System.out.println("ThreadB get Value = " + Tools.t1.get());
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Run {
    public static void main(String[] args) {
        try {
            ThreadA a = new ThreadA();
            ThreadB b= new ThreadB();
            a.start();
            b.start();
            for (int i = 0; i < 20; i++) {
                Tools.t1.set("Main" + (i+1));
                Thread.sleep(200);
                System.out.println("Main get Value = " + Tools.t1.get());
            }
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
類ThreadLocal存儲每一個線程的私有數(shù)據(jù)

雖然三個線程都向t1對象中set()數(shù)據(jù)值,但每個線程還是能取出自己的數(shù)據(jù)。

3.4 解決get() 返回null問題
public class ThreadLocalExt extends ThreadLocal{
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     * <p>
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    @Override
    protected Object initialValue() {
        return "我是默認值 第一次get不再為null";
    }
}

class run{
    public static ThreadLocalExt t1 = new ThreadLocalExt();

    public static void main(String[] args) {
        if(t1.get() == null) {
            System.out.println("從未放過值");
            t1.set("我的值");
        }
        System.out.println(t1.get());
        System.out.println(t1.get());
    }
}
運行結(jié)果:
我是默認值 第一次get不再為null
我是默認值 第一次get不再為null

此案例僅僅證明main線程有自己的值,那其他線程是否會有自己的初始值呢?

3.5 再次驗證線程變量的隔離性
public class Tools {
    public static ThreadLocalExt t1 = new ThreadLocalExt();
}
class ThreadLocalExt extends ThreadLocal {
    /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     * <p>
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    @Override
    protected Object initialValue() {
        return new Date().getTime();
    }
}
class ThreadA extends Thread {
    @Override
    public  void run() {
        try{
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA 線程中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class Run {
    public static void main(String[] args) {
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在Main線程中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

運行結(jié)果:

運行結(jié)果各有各的值

子線程和父線程各有各自所擁有的值;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓢省,一起剝皮案震驚了整個濱河市弄息,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勤婚,老刑警劉巖摹量,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異馒胆,居然都是意外死亡缨称,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人兔魂,你說我怎么就攤上這事竞端。” “怎么了殖告?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵村生,是天一觀的道長冰木。 經(jīng)常有香客問我沿量,道長浪慌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任朴则,我火速辦了婚禮权纤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佛掖。我一直安慰自己妖碉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布芥被。 她就那樣靜靜地躺著欧宜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拴魄。 梳的紋絲不亂的頭發(fā)上冗茸,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天,我揣著相機與錄音匹中,去河邊找鬼夏漱。 笑死,一個胖子當(dāng)著我的面吹牛顶捷,可吹牛的內(nèi)容都是我干的挂绰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼服赎,長吁一口氣:“原來是場噩夢啊……” “哼葵蒂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起重虑,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤践付,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缺厉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體永高,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年提针,在試婚紗的時候發(fā)現(xiàn)自己被綠了命爬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辐脖,死狀恐怖遇骑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情揖曾,我是刑警寧澤落萎,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站炭剪,受9級特大地震影響练链,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜奴拦,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一媒鼓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧错妖,春花似錦绿鸣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至痴施,卻和暖如春擎厢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背辣吃。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工动遭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人神得。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓厘惦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親哩簿。 傳聞我的和親對象是個殘疾皇子宵蕉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容