<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Java Votary

      BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
      48 隨筆 :: 1 文章 :: 80 評論 :: 0 Trackbacks

    ThreadLocal是什么

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

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

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

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

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

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

    ThreadLocal的接口方法

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

    • void set(Object value)

    設置當前線程的線程局部變量的值。

    • public Object get()

    該方法返回當前線程所對應的線程局部變量。

    • public void remove()

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

    • protected Object initialValue()

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

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

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

    代碼清單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);②返回本線程對應的變量

    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實現版本顯得比較幼稚,但它和JDK所提供的ThreadLocal類在實現思路上是相近的。

    一個TheadLocal實例

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

    代碼清單2 SequenceNumber

    package com.baobaotao.basic;

    public class SequenceNumber {

    通過匿名內部類覆蓋ThreadLocalinitialValue()方法,指定初始值

    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,各自產生序列號

    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()+"]");

    }

    }

    }

    }

     

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

    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]

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

    Thread同步機制的比較

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

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

    而ThreadLocal則從另一個角度來解決多線程的并發(fā)訪問。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線 程對數據的訪問沖突。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編 寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal。

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

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

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

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

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

    通通透透理解ThreadLocal

    1同一線程貫通三層

    這樣你就可以根據需要,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應的調用線程中,所有關聯的對象引用到的都是同一個變量。

    下面的實例能夠體現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)”進行改造:

    代碼清單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沒有本線程對應的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中獲取線程對應的Connection

    Statement stat = getConnection().createStatement();

    }

    }

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

    當然,這個例子本身很粗糙,將Connection的ThreadLocal直接放在DAO只能做到本DAO的多個方法共享Connection時 不發(fā)生線程安全問題,但無法和其它DAO共用同一個Connection,要做到同一事務多DAO共享同一Connection,必須在一個共同的外部類 使用ThreadLocal保存Connection。

    小結

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

    posted on 2009-07-01 12:15 Dion 閱讀(344) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發(fā)表評論。


    網站導航:
     
    主站蜘蛛池模板: 亚洲色婷婷综合久久| 亚洲婷婷综合色高清在线| 国产jizzjizz视频全部免费| 黄页网址大全免费观看12网站 | 大学生一级毛片免费看| 亚洲A∨精品一区二区三区下载| 亚洲国产天堂久久久久久| 美丽姑娘免费观看在线观看中文版 | 色视频色露露永久免费观看| 亚洲婷婷在线视频| 国产嫩草影院精品免费网址| 亚洲熟女综合色一区二区三区| 免费人成网站在线观看不卡| 91亚洲自偷在线观看国产馆| 久久最新免费视频| 亚洲一区二区高清| 亚洲国产精品网站在线播放| 野花高清在线观看免费3中文| 亚洲国产精品99久久久久久| 亚洲一区视频在线播放| 91大神在线免费观看| 亚洲AV日韩精品久久久久| 少妇高潮太爽了在线观看免费| 国产精品亚洲专一区二区三区| 国产免费久久精品丫丫| 国产亚洲精品美女久久久| 中文字幕亚洲免费无线观看日本| 中文日韩亚洲欧美制服| 久久亚洲精品无码av| 亚洲精品无码MV在线观看| 午夜国产精品免费观看 | 亚洲综合AV在线在线播放 | 中文字幕亚洲激情| 亚洲一区二区免费视频| 中文字幕不卡亚洲| 在线观看无码AV网站永久免费| 亚洲国产免费综合| 亚洲乱理伦片在线观看中字| 免费成人在线电影| 欧洲精品码一区二区三区免费看| 亚洲AV无码之日韩精品|