ThreadLocal類使用方法

一癞蚕、概述

ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線程的本地實(shí)現(xiàn)版本辉哥,它并不是一個(gè)Thread桦山,而是threadlocalvariable(線程局部變量)。也許把它命名為ThreadLocalVar更加合適醋旦。線程局部變量(ThreadLocal)其實(shí)的功用非常簡(jiǎn)單恒水,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是Java中一種較為特殊的線程綁定機(jī)制饲齐,是每一個(gè)線程都可以獨(dú)立地改變自己的副本钉凌,而不會(huì)和其它線程的副本沖突。

從線程的角度看捂人,每個(gè)線程都保持一個(gè)對(duì)其線程局部變量副本的隱式引用御雕,只要線程是活動(dòng)的并且 ThreadLocal 實(shí)例是可訪問的矢沿;在線程消失之后,其線程局部實(shí)例的所有副本都會(huì)被垃圾回收(除非存在對(duì)這些副本的其他引用)饮笛。

通過ThreadLocal存取的數(shù)據(jù)咨察,總是與當(dāng)前線程相關(guān),也就是說福青,JVM 為每個(gè)運(yùn)行的線程摄狱,綁定了私有的本地實(shí)例存取空間,從而為多線程環(huán)境常出現(xiàn)的并發(fā)訪問問題提供了一種隔離機(jī)制无午。

ThreadLocal是如何做到為每一個(gè)線程維護(hù)變量的副本的呢媒役?其實(shí)實(shí)現(xiàn)的思路很簡(jiǎn)單,在ThreadLocal類中有一個(gè)Map宪迟,用于存儲(chǔ)每一個(gè)線程的變量的副本酣衷。

概括起來說,對(duì)于多線程資源共享的問題次泽,同步機(jī)制采用了“以時(shí)間換空間”的方式穿仪,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量意荤,讓不同的線程排隊(duì)訪問啊片,而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問而互不影響玖像。

二紫谷、API說明

ThreadLocal()

創(chuàng)建一個(gè)線程本地變量。

T get()

返回此線程局部變量的當(dāng)前線程副本中的值捐寥,如果這是線程第一次調(diào)用該方法笤昨,則創(chuàng)建并初始化此副本。

protected T initialValue()

返回此線程局部變量的當(dāng)前線程的初始值握恳。最多在每次訪問線程來獲得每個(gè)線程局部變量時(shí)調(diào)用此方法一次瞒窒,即線程第一次使用 get() 方法訪問變量的時(shí)候。如果線程先于 get 方法調(diào)用 set(T) 方法乡洼,則不會(huì)在線程中再調(diào)用 initialValue 方法汽烦。

若該實(shí)現(xiàn)只返回 null徽缚;如果程序員希望將線程局部變量初始化為 null 以外的某個(gè)值芳来,則必須為 ThreadLocal 創(chuàng)建子類困肩,并重寫此方法糕簿。通常走哺,將使用匿名內(nèi)部類箫锤。initialValue 的典型實(shí)現(xiàn)將調(diào)用一個(gè)適當(dāng)?shù)臉?gòu)造方法搁痛,并返回新構(gòu)造的對(duì)象泞歉。

void remove()

移除此線程局部變量的值逼侦。這可能有助于減少線程局部變量的存儲(chǔ)需求匿辩。如果再次訪問此線程局部變量,那么在默認(rèn)情況下它將擁有其 initialValue榛丢。

void set(T value)

將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值铲球。許多應(yīng)用程序不需要這項(xiàng)功能,它們只依賴于 initialValue() 方法來設(shè)置線程局部變量的值晰赞。

在程序中一般都重寫initialValue方法稼病,以給定一個(gè)特定的初始值。

三掖鱼、典型實(shí)例

1然走、Hiberante的Session 工具類HibernateUtil

這個(gè)類是Hibernate官方文檔中HibernateUtil類,用于session管理戏挡。

public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定義SessionFactory

    static {
        try {
            // 通過默認(rèn)配置文件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還沒有打開拆檬,則新開一個(gè)Session
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);         //將新開的Session保存到線程局部變量中
        }
        return s;
    }

    public static void closeSession() throws HibernateException {
        //獲取線程局部變量,并強(qiáng)制轉(zhuǎn)換為Session類型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

在這個(gè)類中妥凳,由于沒有重寫ThreadLocal的initialValue()方法竟贯,則首次創(chuàng)建線程局部變量session其初始值為null,第一次調(diào)用currentSession()的時(shí)候猾封,線程局部變量的get()方法也為null澄耍。因此,對(duì)session做了判斷晌缘,如果為null齐莲,則新開一個(gè)Session,并保存到線程局部變量session中磷箕,這一步非常的關(guān)鍵选酗,這也是“public static final ThreadLocal session = new ThreadLocal()”所創(chuàng)建對(duì)象session能強(qiáng)制轉(zhuǎn)換為Hibernate Session對(duì)象的原因。

2岳枷、另外一個(gè)實(shí)例

創(chuàng)建一個(gè)Bean芒填,通過不同的線程對(duì)象設(shè)置Bean屬性,保證各個(gè)線程Bean對(duì)象的獨(dú)立性空繁。

/**
* 學(xué)生
*/
public class Student {
    private int age = 0;   //年齡
    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

/**
* 多線程下測(cè)試程序
*/
public class ThreadLocalDemo implements Runnable {
    //創(chuàng)建線程局部變量studentLocal殿衰,在后面你會(huì)發(fā)現(xiàn)用來保存Student對(duì)象
    private final static ThreadLocal studentLocal = new ThreadLocal();

    public static void main(String[] agrs) {
        ThreadLocalDemo td = new ThreadLocalDemo();
        Thread t1 = new Thread(td, "a");
        Thread t2 = new Thread(td, "b");
        t1.start();
        t2.start();
    }

    public void run() {
        accessStudent();
     }

    /**
     * 示例業(yè)務(wù)方法,用來測(cè)試
     */
    public void accessStudent() {
        //獲取當(dāng)前線程的名字
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running!");

        //產(chǎn)生一個(gè)隨機(jī)數(shù)并打印
        Random random = new Random();
        int age = random.nextInt(100);
        System.out.println("thread " + currentThreadName + " set age to:" + age);

        //獲取一個(gè)Student對(duì)象盛泡,并將隨機(jī)數(shù)年齡插入到對(duì)象屬性中
        Student student = getStudent();
        student.setAge(age);
        System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge());
        try {
            Thread.sleep(500);
        }catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
    }

    protected Student getStudent() {
        //獲取本地線程變量并強(qiáng)制轉(zhuǎn)換為Student類型
        Student student = (Student) studentLocal.get();
        //線程首次執(zhí)行此方法的時(shí)候闷祥,studentLocal.get()肯定為null
        if (student == null) {
            //創(chuàng)建一個(gè)Student對(duì)象,并保存到本地線程變量studentLocal中
            student = new Student();
            studentLocal.set(student);
        }
        return student;
    }
}

運(yùn)行結(jié)果:

a is running!
thread a set age to:76
b is running!
thread b set age to:27
thread a first read age is:76
thread b first read age is:27
thread a second read age is:76
thread b second read age is:27

可以看到a傲诵、b兩個(gè)線程age在不同時(shí)刻打印的值是完全相同的凯砍。這個(gè)程序通過妙用ThreadLocal箱硕,既實(shí)現(xiàn)多線程并發(fā),又兼顧數(shù)據(jù)的安全性悟衩。

四剧罩、總結(jié)

ThreadLocal使用場(chǎng)合主要解決多線程中數(shù)據(jù)因并發(fā)產(chǎn)生不一致問題。ThreadLocal為每個(gè)線程的中并發(fā)訪問的數(shù)據(jù)提供一個(gè)副本座泳,通過訪問副本來運(yùn)行業(yè)務(wù)惠昔,這樣的結(jié)果是耗費(fèi)了內(nèi)存,但大大減少了線程同步所帶來性能消耗钳榨,也減少了線程并發(fā)控制的復(fù)雜度舰罚。

ThreadLocal不能使用原子類型,只能使用Object類型薛耻。ThreadLocal的使用比synchronized要簡(jiǎn)單得多营罢。

ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別饼齿。synchronized是利用鎖的機(jī)制饲漾,使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問。而ThreadLocal為每一個(gè)線程都提供了變量的副本缕溉,使得每個(gè)線程在某一時(shí)間訪問到的并不是同一個(gè)對(duì)象考传,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。而Synchronized卻正好相反证鸥,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享僚楞。

Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離枉层。

當(dāng)然ThreadLocal并不能替代synchronized,它們處理不同的問題域泉褐。Synchronized用于實(shí)現(xiàn)同步機(jī)制,比ThreadLocal更加復(fù)雜鸟蜡。

五膜赃、ThreadLocal使用的一般步驟

1、在多線程的類(如ThreadDemo類)中揉忘,創(chuàng)建一個(gè)ThreadLocal對(duì)象threadXxx跳座,用來保存線程間需要隔離處理的對(duì)象xxx。

2泣矛、在ThreadDemo類中疲眷,創(chuàng)建一個(gè)獲取要隔離訪問的數(shù)據(jù)的方法getXxx(),在方法中判斷您朽,若ThreadLocal對(duì)象為null時(shí)候咪橙,應(yīng)該new()一個(gè)隔離訪問類型的對(duì)象,并強(qiáng)制轉(zhuǎn)換為要應(yīng)用的類型。

3美侦、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數(shù)據(jù)魂奥,這樣可以保證每個(gè)線程對(duì)應(yīng)一個(gè)數(shù)據(jù)對(duì)象菠剩,在任何時(shí)刻都操作的是這個(gè)對(duì)象。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末耻煤,一起剝皮案震驚了整個(gè)濱河市具壮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哈蝇,老刑警劉巖棺妓,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異炮赦,居然都是意外死亡怜跑,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門吠勘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來性芬,“玉大人,你說我怎么就攤上這事剧防≈诧保” “怎么了?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵峭拘,是天一觀的道長(zhǎng)俊庇。 經(jīng)常有香客問我,道長(zhǎng)鸡挠,這世上最難降的妖魔是什么辉饱? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮宵凌,結(jié)果婚禮上鞋囊,老公的妹妹穿的比我還像新娘。我一直安慰自己瞎惫,他們只是感情好溜腐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瓜喇,像睡著了一般挺益。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乘寒,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天望众,我揣著相機(jī)與錄音,去河邊找鬼。 笑死烂翰,一個(gè)胖子當(dāng)著我的面吹牛夯缺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播甘耿,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼踊兜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了佳恬?” 一聲冷哼從身側(cè)響起捏境,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毁葱,沒想到半個(gè)月后垫言,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倾剿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年筷频,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柱告。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡截驮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出际度,到底是詐尸還是另有隱情葵袭,我是刑警寧澤,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布乖菱,位于F島的核電站坡锡,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏窒所。R本人自食惡果不足惜鹉勒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吵取。 院中可真熱鬧禽额,春花似錦、人聲如沸皮官。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捺氢。三九已至藻丢,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間摄乒,已是汗流浹背悠反。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國打工残黑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斋否。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓梨水,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親茵臭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冰木,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

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