ThreadLocal應用場景及實例

一、對ThreadLocal理解

ThreadLocal提供一個方便的方式,可以根據不同的線程存放一些不同的特徵屬性,可以方便的在線程中進行存取。

二、以session爲例來理解ThreadLocal

在web開發的session中,不同的線程對應不同的session,那麼如何針對不同的線程獲取對應的session呢?

我們可以設想了如下兩種方式:

1.在action中創建session,然後傳遞給Service,Service再傳遞給Dao,很明顯,這種方式將使代碼變得臃腫複雜。

2.創建一個靜態的map,鍵對應我們的線程,值對應session,當我們想獲取session時,只需要獲取map,然後根據當前的線程就可以獲取對應的值。

我們看看Hibernate中是如何實現這種情況的:
  在這裏插入圖片描述
  在Hibernate中是通過使用ThreadLocal來實現的。在getSession方法中,如果ThreadLocal存在session,則返回session,否則創建一個session放入ThreadLocal中。

總結一下就是在ThreadLocal中存放了一個session。

爲什麼我們在ThreadLocal存放一個session,這個session就會與一個線程對應呢?

實際上ThreadLocal中並沒有存放任何的對象或引用,在上面的的代碼中ThreadLocal的實例threadSession只相當於一個標記的作用。而存放對象的真正位置是正在運行的Thread線程對象,每個Thread對象中都存放着一個ThreadLocalMap類型threadLocals對象,這是一個映射表map,這個map的鍵是一個ThreadLocal對象,值就是我們想存的局部對象。

我們以上面的代碼爲例分析一下:

當我們往ThreadLocal中存放變量的時候發生了什麼?

即這行代碼時。
在這裏插入圖片描述
我們看下ThreadLocal的源碼中set()方法的實現。
在這裏插入圖片描述
如果把這些代碼簡化的話就一句

Thread.currentThread().threadLocals.set(this,value);

Thread.currentThread()獲取當前的線程

threadLocals就是我們上面說的每個線程對象中用於存放局部對象的map

所以set()就是獲取到當前線程的map然後把值放進去,我們發現鍵是this,也就是當前的ThreadLocal對象,可以發現ThreadLocal對象就是一個標記的作用,我們根據這個標記找到對應的局部對象。

如果對比get()方法,可以發現原理都差不多,都是對線程中的threadLocals這個map的操作,我就不解釋了。

ThreadLocal就是一個標記的作用,當我們在線程中使用ThreadLocal的set()或者get()方法時,其實是在操作我們線程自帶的threadLocals這個map,多個線程的時候自然就有多個map,這些map互相獨立,但是,這些map都是根據一個ThreadLocal對象(因爲它是靜態的)來作爲鍵存放。

這樣可以在多個線程中,每個線程存放不一樣的變量,我們通過一個ThreadLocal對象,在不同的線程(通過Thread.currentThread()獲取當前線程)中得到不同的值(不同線程的threadLocals不一樣)。

爲什麼threadLocals要是一個map呢?

因爲我們可能會在一個類中聲明多個ThreadLocal的實例,這樣就有多個標記,所以要使用map對應。

總結:

ThreadLocal就是用來在類中聲明的一個標記,然後通過這個標記就根據不同Thread對象存取值。

應用場景:

在線程中存放一些就像session的這種特徵變量,會針對不同的線程,有不同的值。

舉個栗子:

ThreadLocal:用於實現線程內部的數據共享叫線程共享(對於同一個線程內部數據一致),即相同的一段代碼 多個線程來執行 ,每個線程使用的數據只與當前線程有關。

實現原理:ThreadLocal相當於一個map 當前線程 存儲當前的變量的時候 map.put(確定線程的唯一值(比如變量名稱),變量),然後獲取的時候直接拿過來就行

一般用法:定義一個全局變量ThreadLoacl t 將新建線程要使用的變量 存進去 比如

1.當存儲的爲基本變量或者包裝對象時

package com.yanghs.test.traditional;
 
/**
 * @author yanghs
 * @Description:
 * @date 2018/3/31 16:24
 */
public class ThreadLocalTest  {
    /*定義一個全局變量 來存放線程需要的變量*/
    public static ThreadLocal<Integer> ti = new ThreadLocal<Integer>();
    public static void main(String[] args) {
        /*創建兩個線程*/
        for(int i=0; i<2;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Double d = Math.random()*10;
                    /*存入當前線程獨有的值*/
                    ti.set(d.intValue());
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
    static class A{
        public void get(){
            /*取得當前線程所需要的值*/
            System.out.println(ti.get());
        }
    }
    static class B{
        public void get(){
            /*取得當前線程所需要的值*/
            System.out.println(ti.get());
        }
    }
}

2.當存儲的爲對象時 就是數據集合 比如前臺傳過來的參數,每一個人傳過來的 都是這個人獨有的,才能保證數據準確性,抽取業務數據爲一個對象

class ThreadLocalDemo{
    /*把線程相關的部分內聚到 類裏面  相當於map 每個類是對應key*/
    private static ThreadLocal<ThreadLocalDemo> t = new ThreadLocal<ThreadLocalDemo>();
    private ThreadLocalDemo(){}
    public static ThreadLocalDemo getThreadInstance(){
        ThreadLocalDemo threadLocalDemo  = t.get();
        if(null == threadLocalDemo){//當前線程無綁定的對象時,直接綁定一個新的對象
            threadLocalDemo = new ThreadLocalDemo();
            t.set(threadLocalDemo);
        }
        return threadLocalDemo;
    }
 
    private String name;
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

把ThreadLocal 放在業務對象裏面提現高內聚,實現的目的是每一個線程都有一個獨立的ThreadLocalDemo對象。 使用的時候只需要 ThreadLocalDemo.getInstance()就可以得到當前線程的所需要的值。

package com.yanghs.test.traditional;
 
/**
 * @author yanghs
 * @Description:
 * @date 2018/3/31 16:24
 */
public class ThreadLocalTest  {
    public static void main(String[] args) {
        for(int i=0; i<2;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Double d = Math.random()*10;
                    ThreadLocalDemo.getThreadInstance().setName("name"+d);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
    static class A{
        public void get(){
            System.out.println(ThreadLocalDemo.getThreadInstance().getName());
        }
    }
    static class B{
        public void get(){
            System.out.println(ThreadLocalDemo.getThreadInstance().getName());
        }
    }
}

其實Struts2的ActionContext就是使用這種方式綁定數據。

參考博客:http://www.iteye.com/topic/103804
https://blog.csdn.net/Ryice/article/details/79771226

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章