1. 程式人生 > >java中ThreadLoacl解析

java中ThreadLoacl解析

一:ThreadLocal變數的解釋
ThreadLocal型別是一個執行緒變數,其並不是用來像lock/sychronized一樣解決java多執行緒中變數共享的安全性問題的,並且使用ThreadLocal型別變數並不一定能保證共享物件上的安全併發(放入Thread類的變數ThreadLocalMap threadLocals中的物件即value值,必須是在當前執行緒方法中所建立的區域性變數,或者是在其他地方正確釋出的執行緒安全物件。否則,若放入的物件為多個執行緒所共享的非執行緒安全物件,那麼就會造成併發問題)。

ThreadLocal型別變數最大的用途是用來儲存執行緒內部的變數,使得線上程的方法呼叫棧上的任何方法中,都可以通過ThreadLocal變數的get方法得到該ThreadLocal型別為key對應的value值。

在Thread類中,定義有

ThreadLocal.ThreadLocalMap threadLocals = null;`

即,每個執行的執行緒都會有一個名字為threadLocals的map,其中存放的是key=宣告的ThreadLocal型別的變數引用,value=放入的執行緒變數值/初始化值/null(一般該物件會線上程方法中建立,即為執行緒的區域性變數,為執行緒封閉物件)。
並不是有些地方所說的key=當前執行緒,value=要獲得的值(如果是key=當前執行緒的話,那麼豈不是這個map中僅僅只存放 了一個Entry物件)。
若在程式中宣告有多個ThreadLocal型別的物件引用,那麼線上程的threadLocals所引用的map中就會有多個Entry物件,其key值就為宣告的ThreadLocal物件的引用。

二:ThreadLocal變數在框架中的應用
在Spring中大量使用帶該技術。其就相當於是一個執行緒區域性變數,被封裝線上程Stack中,僅被當前執行緒所使用。並且該執行緒終結之前,在該執行緒方法呼叫鏈的任何一個方法中都可以通過get()方法獲得該變數值。

在MVC構架的Web專案中:
某些變數若不使用ThradLocal處理,則,需要在該執行緒的方法鏈中,將區域性變數一直傳遞下去。
但如果使用ThreadLocal處理,就可以在任何一層通過get方法獲得給變數。
在基於SSH構架的MVC結構的javaWeb專案中,從接受請求到響應,都屬於是一個執行緒。

SSH的javaWeb處理請求執行緒呼叫方法棧
那麼,如果在Action層建立一個ThreadLocal變數,這在Service層和Dao層都可以通過get方法獲得該變數。

三:執行緒使用ThreadLocal物件建立執行緒內部變數時的呼叫順序:

  1. 第一次呼叫ThreadLocal型別物件的set或者get方法時,會根據當前執行緒所擁有的ThreadLocal型別物件作為key,null或者若重寫initialValue()方法自定義初始值為value,建立一個ThreadLocalMap(ThreadLocal key,Object value)型別的map物件,並賦值給當前執行緒的threadLocals變數。
  2. 後續每次對get或者set方法的呼叫都會直接獲得當前執行緒的threadLocals變數,即第一步初始化的ThreadLocalMap型別的map。然後根據當前呼叫物件this(即宣告的ThreadLocal型別變數的引用)作為key,去找到對應的value值返回。

通過下面的例子,描述ThreadLocal的呼叫順序:
SequenceNumber 為一個計數類,內部有一個ThreadLocal型別變數,並重寫其initialValue方法。

public class SequenceNumber {

    // ①覆蓋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();
    }
}

A,當執行緒A第一次呼叫getNextNum()方法時,會呼叫seqNum(ThreadLocal型別物件)的get()方法,jdk中原始碼如下:

 /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

  /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
  1. 獲得當前執行緒A。
  2. 獲得當前執行緒A的threadLocals變數,為ThreadLocalMap型別,jdk中Thread類中原始碼如下。
  /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

3.此時,執行緒A中的threadLocals變數還沒有初始化,故為null,即會執行return setInitialValue();

  /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
  1. 獲得value值,會呼叫SequenceNumber類中定義的initialValue()方法,返回value=0;
  2. 獲得當前執行緒A
  3. 獲得當前執行緒A的threadLocals變數,此時還為null
  4. 故執行createMap(t, value)方法;其中t為當前執行緒A,value=0
  /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     * @param map the map to store.
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
  1. 這裡會根據形參t即是執行緒A。建立ThreadLocalMap物件,並將其賦給執行緒A的threadLocals變數。
  2. 這裡的this為當前執行緒中的TheadLocal型別的物件,即為SequenceNumber類中的seqNum物件。
 /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
  1. 初始化map中的桶陣列table;
  2. 根據firstkey=SequenceNumbe.seqNum物件的hash值,定位到陣列中的位置;
  3. key=ThreadLocal物件,value=0建立Entry物件;
  4. map初始化完畢。

B,第一次呼叫get方法時,返回初始值0,然後再呼叫set方法:

 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
  1. 首先獲得當前執行緒A;
  2. 獲得執行緒A的threadLocals變數,即為上述一中說建立的ThreadLocalMap型別的map;
  3. 此時threadLocals不為null,呼叫map.set(this, value)方法,其中this=SequenceNumbe.seqNum物件,value=1;即定位到的Entry還是第一次呼叫get方法初始化時的那個Entry物件,這個threadLocals(ThreadLocalMap型別的map)中就存放了一個Entry物件,其key=SequenceNumbe.seqNum,value=1;

    C,到此,若執行緒A執行完畢那麼就會清除執行緒A中的threadLocals變數;

 /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
  1. 將thradLocals變數置為null;

那麼到此,執行緒A對於ThreadLocal類的使用就結束了。

PS:簡單的說,ThreadLocal變數就是Thread類中(ThreadLocalMap)threadLocals的key,而value就是需要使用到的執行緒區域性變數.。關於value值的設定有兩個途徑:

  • 初始化的時候設定。預設為null,或者是自己重寫初始化方法。
  • 線上程的方法中呼叫ThreadLocal變數的set方法。
    那麼在該執行緒棧之後的方法中,都可以通過get方法來獲得ThreaadLocal變數對應的執行緒區域性變數,該變數封閉在該執行緒中,其他執行緒無法獲得。但是,在上述兩個途徑中設定執行緒區域性變數時,必須注意該物件的來源:

  • 一般是線上程方法中建立的物件或者基本資料型別(自動轉換成包裝類)。這樣,在每個執行緒中都是自己的私有資料,而不會被其他執行緒獲得。

  • 若該物件本身為共享的物件,那麼使用ThreadLocal並不能解決併發問題,這樣會使每個執行緒通過ThreadLocal變數拿到的還是同一個共享物件,與將該共享物件設定為static效果一樣。並且根本就沒有必要將共享的物件放入到ThreadLocal執行緒本地變數中,直接設定為static即可。

PS:一篇關於ThreadLocal講解的還不錯的文章:http://www.iteye.com/topic/103804
其中也提到ThreadLocalMap型別的map中並不簡單是存放的物件的拷貝/複製。而是線上程方法中建立的執行緒封閉的區域性變數,不管是基本型別變數還是物件。並且該map是以ThreadLocal型別物件引用為key值,每個執行緒各自一個ThreadLocalMap型別的map。

ps:後續還有一篇關於ThreadLocal變數使用的注意事項,使用ThreadLocal變數並不能一定保證執行緒安全。

附:上述分析的原始碼

package thread;

import static org.junit.Assert.*;

import java.util.Random;

import org.junit.Test;

public class SequenceNumber {

    // ①覆蓋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();
    }

    //建立第二個threadLocal型別的變數
    private static ThreadLocal<String> name = new ThreadLocal<String>(){

        @Override
        protected String initialValue() {
            // TODO Auto-generated method stub
            return "lecky";
        }

    };

    public static void main(String[] args){
        SequenceNumber sn = new SequenceNumber();
        System.out.println(sn);
        // ③ 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() {
            System.out.println("初始化值為="+name.get());
            name.set(Thread.currentThread().getName());
            for (int i = 0; i < 3; i++) {// ④每個執行緒打出3個序列值
                System.out.println("當前執行緒="+name.get());
                System.out.println("thread[" + Thread.currentThread().getName()
                        + "] sn[" + sn.getNextNum() + "]");
            }
        }
    }
}

相關推薦

javaThreadLoacl解析

一:ThreadLocal變數的解釋 ThreadLocal型別是一個執行緒變數,其並不是用來像lock/sychronized一樣解決java多執行緒中變數共享的安全性問題的,並且使用ThreadLocal型別變數並不一定能保證共享物件上的安全併發(放入Th

Java原生解析JavaScript指令碼語言

前言 由於一些需求,現在需要在Java中解析字串,做一些簡單的算數運算和邏輯運算,那麼最先想的是模板引擎這個東西,但是Java中的模板引擎是針對View層的,也就是JSP的,在Service層中使用不是太方便,因此選用了原生的JavaScript指令碼解析引擎。實際上Jav

javaJSON解析(字典裡套用字典)

//獲取到第一層解析結果 String value = contentJson.getString("Value"); JSONObject valueJSON = new JSONObject(value); //獲取到第二層解析結果 String issueDeck =

javaDOM解析xml檔案

本文介紹瞭如何利用DOM(即Document Object Model文件物件模型)解析xml檔案。 首先有一個xml檔案: <?xml version=\"1.0\" encoding=\"UTF-8\" ?> <User> <city

JavaJSON解析

JSON資料解析 JSON(JavaScript Object Notation) 是一種輕量級的資料交換格式。 易於人閱讀和編寫。同時也易於機器解析和生成。 它基於JavaScript Programming Language, Standard EC

java學習筆記——java對象的創建,初始化,引用的解析

初始 學習筆記 style article 學習 base 表達 如果 bsp 如果有一個A類。 1、例如以下表達式: A a1 = new A(); 那麽A是類,a1是引用。new A()是對象。僅僅是a1這個引用指向了new A()這個對象。 2、又如: A

Java的static關鍵字解析

而且 類繼承 產生 編程思想 類名 作用域 com c/c++ 毫無   static關鍵字是很多朋友在編寫代碼和閱讀代碼時碰到的比較難以理解的一個關鍵字,也是各大公司的面試官喜歡在面試時問到的知識點之一。下面就先講述一下static關鍵字的用法和平常容易誤解的地方,最後列

Java的訪問權限解析

logs java語言 mil style 解析 sta 重要 技術分享 [] 在Java中不同的對象和類擁有不同的訪問權限,所以在java中對不同的類和對象進行權限的設置顯得尤為重要. java中的權限主要分為四種,public,protect,private,和defa

java關於String和StringBuffer的問題與解析

構造 表達式 數據 str 字符數 stringbu 傳遞 數值 動態 問題一:String 和 StringBuffer 的區別JAVA 平臺提供了兩個類: String 和 StringBuf fer ,它們可以儲存和操作字符串,即包含多個字符的字符數據。這個 Stri

Java高級-解析Java的多線程機制

分配 優先 恢復 需要 java應用程序 成員變量 函數 分布式 方法 線程的狀態控制 在這裏需要明確的是:無論 采用繼承Thread類還是實現Runnable接口來實現應用程序的多線程能力,都需要在該類中定義用於完成實際功能的run方法,這個run方法稱為 線程體(Th

java使用dom4j解析xml

() while 9.png eval ted eva har main root 創建xml文檔並輸出到文件 import java.io.File; import java.io.FileOutputStream; import org.dom4j.Documen

解析Java的String、StringBuilder、StringBuffer類(一)

world! index ret ofb body 理解 rgs private 引入 引言 String 類及其相關的StringBuilder、StringBuffer 類在 Java 中的使用相當的多,在各個公司的面試中也是必不可少的。因此,在本周,我打算花費一些時間

Javaequals和==的解析

實現 sting 應用 告訴 strong str intern println 不存在 java中的數據類型,可分為兩類: 1.基本數據類型,也稱原始數據類型。byte,short,char,int,long,float,double,boolean 他們之間的比較,

解析Javafinal關鍵字的各種用法

col 後序 blog str 訪問 人類 依然 fin 可能 首先,我們可以從字面上理解一下final這個英文單詞的中文含義:“最後的,最終的; 決定性的; 不可更改的;”。顯然,final關鍵詞如果用中文來解釋,“不可更改的”更為合適。當你在編寫程序,可能

Java使用org.json和json-lib解析JSON

contents load user cti clas and arraylist 源碼 fur 文章目錄 [隱藏] 一。JavaProject中org.json解析JSON 1.JSON的org.son-api下載 1)JSON網址2

Javastatic關鍵字解析

地方 通過 特性 inf 優化 href compare 筆試 star Java中的static關鍵字解析   static關鍵字是很多朋友在編寫代碼和閱讀代碼時碰到的比較難以理解的一個關鍵字,也是各大公司的面試官喜歡在面試時問到的知識點之一。下面就先講述一下static

Java Thread的sleep、join方法解析

開始 system sleep main gen 解析 等待時間 calling trace 1.Thread中sleep方法作用是使當前線程等待,其他線程開始執行,如果有線程鎖,sleep不會讓出鎖 沒有加鎖代碼如下: public class Synchronized

JavaXML的解析方式

轉載自 : https://www.cnblogs.com/longqingyang/p/5577937.html 簡介   XML是一種通用的資料交換格式,它的平臺無關性、語言無關性、系統無關性、給資料整合與互動帶來了極大的方便。XML在不同的語言環境中解析方式都是一

JAVA解析JSON物件裡包含的JSON陣列

例如現在有這樣一個Json String Value={"data":[{"school_name":"西北農林科技大學","school_id":"8"},{"school_name":"西北大學","school_id":"6"},{"school_name":"西北工業大學",

JavacompareTo用法及原始碼解析

最近遇到一個問題,在日期比較的時候,很麻煩,因為日期比較沒有大於等於,只有大於或者小於,這就導致在比較時間的時候特別麻煩,而且還要由string轉成date格式才能比較,下面是我使用compareTo比較時間字串的程式碼: String putStartTime = Date