深入研究JAVA ThreadLocal類

來源:互聯網
上載者:User

標籤:blog   http   java   使用   os   io   檔案   資料   

深入研究java.lang.ThreadLocal類

一、概述

ThreadLocal是什麼呢?其實ThreadLocal並非是一個線程的本地實現版本,它並不是一個Thread,而是 threadlocalvariable(線程局部變數)。也許把它命名為ThreadLocalVar更加合適。線程局部變數 (ThreadLocal)其實的功用非常簡單,就是為每一個使用該變數的線程都提供一個變數值的副本,是Java中一種較為特殊的線程綁定機制,是每一個線程都可以獨立地改變自己的副本,而不會和其它線程的副本衝突。

從線程的角度看,每個線程都保持一個對其線程局部變數副本的隱式引用,只要線程是活動的並且 ThreadLocal 執行個體是可訪問的;線上程消失之後,其線程局部執行個體的所有副本都會被記憶體回收(除非存在對這些副本的其他引用)。

通過ThreadLocal存取的資料,總是與當前線程相關,也就是說,JVM 為每個啟動並執行線程,綁定了私人的本地執行個體存取空間,從而為多線程環境常出現的並發訪問問題提供了一種隔離機制。

ThreadLocal是如何做到為每一個線程維護變數的副本的呢?其實實現的思路很簡單,在ThreadLocal類中有一個Map,用於儲存每一個線程的變數的副本。

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

二、API說明

ThreadLocal()

          建立一個執行緒區域變數。

T get()

          返回此線程局部變數的當前線程副本中的值,如果這是線程第一次調用該方法,則建立並初始化此副本。

protected  T initialValue()

          返回此線程局部變數的當前線程的初始值。最多在每次訪問線程來獲得每個線程局部變數時調用此方法一次,即線程第一次使用 get() 方法訪問變數的時候。如果線程先於 get 方法調用 set(T) 方法,則不會線上程中再調用 initialValue 方法。

   若該實現只返回 null;如果程式員希望將線程局部變數初始化為 null 以外的某個值,則必須為 ThreadLocal 建立子類,並重寫此方法。通常,將使用匿名內部類。initialValue 的典型實現將調用一個適當的構造方法,並返回新構造的對象。

void remove()

          移除此線程局部變數的值。這可能有助於減少線程局部變數的儲存需求。如果再次訪問此線程局部變數,那麼在預設情況下它將擁有其 initialValue。

void set(T value)

          將此線程局部變數的當前線程副本中的值設定為指定值。許多應用程式不需要這項功能,它們只依賴於 initialValue() 方法來設定線程局部變數的值。

在程式中一般都重寫initialValue方法,以給定一個特定的初始值。

三、典型執行個體

1、Hiberante的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建立SessionFactory

            sessionFactory = new Configuration().configure().buildSessionFactory();

        } catch (Throwable ex) {

            log.error(“初始化SessionFactory失敗!”, ex);

            throw new ExceptionInInitializerError(ex);

        }

    }

    //建立線程局部變數session,用來儲存Hibernate的Session

    public static final ThreadLocal session = new ThreadLocal();

 

    /**

     * 擷取當前線程中的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 {

        //擷取線程局部變數,並強制轉換為Session類型

        Session s = (Session) session.get();

        session.set(null);

        if (s != null)

            s.close();

    }

}

 

在這個類中,由於沒有重寫ThreadLocal的initialValue()方法,則首次建立線程局部變數session其初始值為null, 第一次調用currentSession()的時候,線程局部變數的get()方法也為null。因此,對session做了判斷,如果為null,則新 開一個Session,並儲存到線程局部變數session中,這一步非常的關鍵,這也是“public static final ThreadLocal session = new ThreadLocal()”所建立對象session能強制轉換為Hibernate Session對象的原因。

2、另外一個執行個體

建立一個Bean,通過不同的線程對象設定Bean屬性,保證各個線程Bean對象的獨立性。

/**

 * Created by IntelliJ IDEA.

 * User: leizhimin

 * Date: 2007-11-23

 * Time: 10:45:02

 * 學生

 */

public class Student {

    private int age = 0;   //年齡

    public int getAge() {

        return this.age;

    }

    public void setAge(int age) {

        this.age = age;

    }

}

/**

 * Created by IntelliJ IDEA.

 * User: leizhimin

 * Date: 2007-11-23

 * Time: 10:53:33

 * 多線程下測試程式

 */

public class ThreadLocalDemo implements Runnable {

    //建立線程局部變數studentLocal,在後面你會發現用來儲存Student對象

    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();

    }

    /**

     * 樣本業務方法,用來測試

     */

    public void accessStudent() {

        //擷取當前線程的名字

        String currentThreadName = Thread.currentThread().getName();

        System.out.println(currentThreadName + ” is running!”);

        //產生一個隨機數並列印

        Random random = new Random();

        int age = random.nextInt(100);

        System.out.println(“thread ” + currentThreadName + ” set age to:” + age);

        //擷取一個Student對象,並將隨機數年齡插入到對象屬性中

        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() {

        //擷取本地線程變數並強制轉換為Student類型

        Student student = (Student) studentLocal.get();

        //線程首次執行此方法的時候,studentLocal.get()肯定為null

        if (student == null) {

            //建立一個Student對象,並儲存到本地線程變數studentLocal中

            student = new Student();

            studentLocal.set(student);

        }

        return student;

    }

}

運行結果:

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兩個線程age在不同時刻列印的值是完全相同的。這個程式通過妙用ThreadLocal,既實現多線程並發,遊兼顧資料的安全性。

四、總結

ThreadLocal使用場合主要解決多線程中資料資料因並發產生不一致問題。ThreadLocal為每個線程的中並發訪問的資料提供一個副本,通過訪問副本來運行業務,這樣的結果是耗費了記憶體,單大大減少了線程同步所帶來效能消耗,也減少了線程並發控制的複雜度。

ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡單得多。

ThreadLocal和Synchonized都用於解決多線程並發訪問。但是ThreadLocal與synchronized有本質的區別。 synchronized是利用鎖的機制,使變數或代碼塊在某一時該只能被一個線程訪問。而ThreadLocal為每一個線程都提供了變數的副本,使得 每個線程在某一時間訪問到的並不是同一個對象,這樣就隔離了多個線程對資料的資料共用。而Synchronized卻正好相反,它用於在多個線程間通訊時 能夠獲得資料共用。

Synchronized用於線程間的資料共用,而ThreadLocal則用於線程間的資料隔離。

當然ThreadLocal並不能替代synchronized,它們處理不同的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。

五、ThreadLocal使用的一般步驟

1、在多線程的類(如ThreadDemo類)中,建立一個ThreadLocal對象threadXxx,用來儲存線程間需要隔離處理的對象xxx。

2、在ThreadDemo類中,建立一個擷取要隔離訪問的資料的方法getXxx(),在方法中判斷,若ThreadLocal對象為null時候,應該new()一個隔離訪問類型的對象,並強制轉換為要應用的類型。

3、在ThreadDemo類的run()方法中,通過getXxx()方法擷取要操作的資料,這樣可以保證每個線程對應一個資料對象,在任何時刻都操作的是這個對象。

原文地址:http://lavasoft.blog.51cto.com/62575/51926/.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.