日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

理解ThreadLocal

系統(tǒng) 2236 0

概述

?

我們知道Spring通過各種DAO模板類降低了開發(fā)者使用各種數(shù)據(jù)持久技術(shù)的難度。這些模板類都是線程安全的,也就是說,多個DAO可以復(fù)用同一個模板實例而不會發(fā)生沖突。

?

我們使用模板類訪問底層數(shù)據(jù),根據(jù)持久化技術(shù)的不同,模板類需要綁定數(shù)據(jù)連接或會話的資源。但這些資源本身是非線程安全的,也就是說它們不能在同一時刻被多個線程共享。

?

雖然模板類通過資源池獲取數(shù)據(jù)連接或會話,但資源池本身解決的是數(shù)據(jù)連接或會話的緩存問題,并非數(shù)據(jù)連接或會話的線程安全問題。

?

按照傳統(tǒng)經(jīng)驗,如果某個對象是非線程安全的,在多線程環(huán)境下,對對象的訪問必須采用synchronized進(jìn)行線程同步。但Spring的DAO模板類并未采用線程同步機(jī)制,因為線程同步限制了并發(fā)訪問,會帶來很大的性能損失。

?

此外,通過代碼同步解決性能安全問題挑戰(zhàn)性很大,可能會增強(qiáng)好幾倍的實現(xiàn)難度。那模板類究竟仰丈何種魔法神功,可以在無需同步的情況下就化解線程安全的難題呢?答案就是ThreadLocal!

?

ThreadLocal在Spring中發(fā)揮著重要的作用,在管理request作用域的Bean、事務(wù)管理、任務(wù)調(diào)度、AOP等模塊都出現(xiàn)了它們的身影,起著舉足輕重的作用。要想了解Spring事務(wù)管理的底層技術(shù),ThreadLocal是必須攻克的山頭堡壘。

?

ThreadLocal是什么

?

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序。

?

ThreadLocal很容易讓人望文生義,想當(dāng)然地認(rèn)為是一個“本地線程”。其實,ThreadLocal并不是一個Thread,而是Thread的局部變量,也許把它命名為ThreadLocalVariable更容易讓人理解一些。

?

當(dāng)使用ThreadLocal維護(hù)變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本。

?

從線程的角度看,目標(biāo)變量就象是線程的本地變量,這也是類名中“Local”所要表達(dá)的意思。

?

線程局部變量并不是Java的新發(fā)明,很多語言(如IBM IBM XL FORTRAN)在語法層面就提供線程局部變量。在Java中沒有提供在語言級支持,而是變相地通過ThreadLocal的類提供支持。

?

所以,在Java中編寫線程局部變量的代碼相對來說要笨拙一些,因此造成線程局部變量沒有在Java開發(fā)者中得到很好的普及。

?

ThreadLocal的接口方法

?

ThreadLocal類接口很簡單,只有4個方法,我們先來了解一下:

  • void set(Object value)

設(shè)置當(dāng)前線程的線程局部變量的值。

  • public Object get()

該方法返回當(dāng)前線程所對應(yīng)的線程局部變量。

  • public void remove()

將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當(dāng)線程結(jié)束后,對應(yīng)該線程的局部變量將自動被垃圾回收,所以顯式調(diào)用該方法清除線程的局部變量并不是必須的操作,但它可以加快內(nèi)存回收的速度。

  • protected Object initialValue()

返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設(shè)計的。這個方法是一個延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時才執(zhí)行,并且僅執(zhí)行1次。ThreadLocal中的缺省實現(xiàn)直接返回一個null。

?

值得一提的是,在JDK5.0中,ThreadLocal已經(jīng)支持泛型,該類的類名已經(jīng)變?yōu)門hreadLocal<T>。API方法 也相應(yīng)進(jìn)行了調(diào)整,新版本的API方法分別是void set(T value)、T get()以及T initialValue()。

?

ThreadLocal是如何做到為每一個線程維護(hù)變量的副本的呢?其實實現(xiàn)的思路很簡單:在ThreadLocal類中有一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值對應(yīng)線程的變量副本。我們自己就可以提供一個簡單的實現(xiàn)版本:

?

代碼清單 1

    SimpleThreadLocal

public class SimpleThreadLocal {

private Map valueMap = Collections.synchronizedMap(new HashMap());

public void set(Object newValue) {

valueMap.put(Thread.currentThread(), newValue);①鍵為線程對象,值為本線程的變量副本

}

public Object get() {

Thread currentThread = Thread.currentThread();

Object o = valueMap.get(currentThread);②返回本線程對應(yīng)的變量

if (o == null && !valueMap.containsKey(currentThread)) {③如果在Map中不存在,放到Map

中保存起來。

o = initialValue();

valueMap.put(currentThread, o);

}

return o;

}

public void remove() {

valueMap.remove(Thread.currentThread());

}

public Object initialValue() {

return null;

}

}

  
?

雖然代碼清單9?3這個ThreadLocal實現(xiàn)版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實現(xiàn)思路上是相近的。

?

一個TheadLocal實例

?

下面,我們通過一個具體的實例了解一下ThreadLocal的具體使用方法。

代碼清單 2

    SequenceNumber

package com.baobaotao.basic;

public class SequenceNumber {

①通過匿名內(nèi)部類覆蓋ThreadLocal的initialValue()方法,指定初始值

private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){

public Integer initialValue(){

return 0;

}

};

②獲取下一個序列值

public int getNextNum(){

seqNum.set(seqNum.get()+1);

return seqNum.get();

}

public static void main(String[] args)

{

SequenceNumber sn = new SequenceNumber();

③ 3個線程共享sn,各自產(chǎn)生序列號

TestClient t1 = new TestClient(sn);

TestClient t2 = new TestClient(sn);

TestClient t3 = new TestClient(sn);

t1.start();

t2.start();

t3.start();

}

private static class TestClient extends Thread

{

private SequenceNumber sn;

public TestClient(SequenceNumber sn) {

this.sn = sn;

}

public void run()

{

for (int i = 0; i < 3; i++) {④每個線程打出3個序列值

System.out.println("thread["+Thread.currentThread().getName()+

"] sn["+sn.getNextNum()+"]");

}

}

}

}
  
?

通常我們通過匿名內(nèi)部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產(chǎn)生一組序列號, 在③處,我們生成3個TestClient,它們共享同一個SequenceNumber實例。運(yùn)行以上代碼,在控制臺上輸出以下的結(jié)果:

?

thread[Thread-2] sn[1]

thread[Thread-0] sn[1]

thread[Thread-1] sn[1]

thread[Thread-2] sn[2]

thread[Thread-0] sn[2]

thread[Thread-1] sn[2]

thread[Thread-2] sn[3]

thread[Thread-0] sn[3]

thread[Thread-1] sn[3]

?

考察輸出的結(jié)果信息,我們發(fā)現(xiàn)每個線程所產(chǎn)生的序號雖然都共享同一個SequenceNumber實例,但它們并沒有發(fā)生相互干擾的情況,而是各自產(chǎn)生獨立的序列號,這是因為我們通過ThreadLocal為每一個線程提供了單獨的副本。

?

Thread同步機(jī)制的比較

?

ThreadLocal和線程同步機(jī)制相比有什么優(yōu)勢呢?ThreadLocal和線程同步機(jī)制都是為了解決多線程中相同變量的訪問沖突問題。

?

在同步機(jī)制中,通過對象的鎖機(jī)制保證同一時間只有一個線程訪問變量。這時該變量是多個線程共享的,使用同步機(jī)制要求程序慎密地分析什么時候?qū)ψ兞窟M(jìn)行讀寫,什么時候需要鎖定某個對象,什么時候釋放對象鎖等繁雜的問題,程序設(shè)計和編寫難度相對較大。

?

而ThreadLocal則從另一個角度來解決多線程的并發(fā)訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線 程對數(shù)據(jù)的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進(jìn)行同步了。

?

ThreadLocal提供了線程安全的共享對象,在編 寫多線程代碼時,可以把不安全的變量封裝進(jìn)ThreadLocal。

?

由于ThreadLocal中可以持有任何類型的對象,低版本JDK所提供的get()返回的是Object對象,需要強(qiáng)制類型轉(zhuǎn)換。但JDK 5.0通過泛型很好的解決了這個問題,在一定程度地簡化ThreadLocal的使用,代碼清單 9 2就使用了JDK 5.0新的ThreadLocal<T>版本。

?

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

?

Spring使用ThreadLocal解決線程安全問題

?

我們知道在一般情況下,只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用 域。就是因為Spring對一些Bean(如RequestContextHolder、 TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)采用 ThreadLocal進(jìn)行處理,讓它們也成為線程安全的狀態(tài),因為有狀態(tài)的Bean就可以在多線程中共享了。

?

一般的Web應(yīng)用劃分為展現(xiàn)層、服務(wù)層和持久層三個層次,在不同的層中編寫對應(yīng)的邏輯,下層通過接口向上層開放功能調(diào)用。在一般情況下,從接收請求到返回響應(yīng)所經(jīng)過的所有程序調(diào)用都同屬于一個線程,如圖9?2所示:

?


理解ThreadLocal

1同一線程貫通三層

?

這樣你就可以根據(jù)需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應(yīng)的調(diào)用線程中,所有關(guān)聯(lián)的對象引用到的都是同一個變量。

?

下面的實例能夠體現(xiàn)Spring對有狀態(tài)Bean的改造思路:

?

代碼清單3 TopicDao:非線程安全

    public class TopicDao {

private Connection conn;①一個非線程安全的變量

public void addTopic(){

Statement stat = conn.createStatement();②引用非線程安全變量

…

}

}

  
?

由于①處的conn是成員變量,因為addTopic()方法是非線程安全的,必須在使用時創(chuàng)建一個新TopicDao實例(非singleton)。下面使用ThreadLocal對conn這個非線程安全的“狀態(tài)”進(jìn)行改造:

?

代碼清單4 TopicDao:線程安全

?

    import java.sql.Connection;

import java.sql.Statement;

public class TopicDao {

①使用ThreadLocal保存Connection變量

private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();

public static Connection getConnection(){

②如果connThreadLocal沒有本線程對應(yīng)的Connection創(chuàng)建一個新的Connection,

并將其保存到線程本地變量中。

if (connThreadLocal.get() == null) {

Connection conn = ConnectionManager.getConnection();

connThreadLocal.set(conn);

return conn;

}else{

return connThreadLocal.get();③直接返回線程本地變量

}

}

public void addTopic() {

④從ThreadLocal中獲取線程對應(yīng)的Connection

Statement stat = getConnection().createStatement();

}

}
  
?

不同的線程在使用TopicDao時,先判斷connThreadLocal.get()是否是null,如果是null,則說明當(dāng)前線程還沒有對 應(yīng)的Connection對象,這時創(chuàng)建一個Connection對象并添加到本地線程變量中;如果不為null,則說明當(dāng)前的線程已經(jīng)擁有了 Connection對象,直接使用就可以了。這樣,就保證了不同的線程使用線程相關(guān)的Connection,而不會使用其它線程的 Connection。因此,這個TopicDao就可以做到singleton共享了。

?

當(dāng)然,這個例子本身很粗糙,將Connection的ThreadLocal直接放在DAO只能做到本DAO的多個方法共享Connection時 不發(fā)生線程安全問題,但無法和其它DAO共用同一個Connection,要做到同一事務(wù)多DAO共享同一Connection,必須在一個共同的外部類 使用ThreadLocal保存Connection。但這個實例基本上說明了Spring對有狀態(tài)類線程安全化的解決思路。

?

小結(jié)

?

ThreadLocal是解決線程安全問題一個很好的思路,它通過為每個線程提供一個獨立的變量副本解決了變量并發(fā)訪問的沖突問題。在很多情況 下,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問題更簡單,更方便,且結(jié)果程序擁有更高的并發(fā)性。

?

[ 轉(zhuǎn)自 : http://www.builder.com.cn/2007/0529/404695.shtml ]

理解ThreadLocal


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 苏尼特右旗| 辽宁省| 通辽市| 隆德县| 赤水市| 桦甸市| 自贡市| 金川县| 天柱县| 孝感市| 阿拉尔市| 广安市| 大方县| 海宁市| 鄂温| 汝州市| 汪清县| 仲巴县| 农安县| 牟定县| 佛教| 长子县| 佛山市| 台南县| 池州市| 南川市| 三都| 格尔木市| 陈巴尔虎旗| 临朐县| 新化县| 寻乌县| 灌云县| 桐城市| 克什克腾旗| 阜新市| 阜城县| 卓尼县| 安陆市| 兴隆县| 桂阳县|