java之ThreadLocal詳解

來源:互聯網
上載者:User

標籤:cond   ace   處理   nal   結構   read   通過   初始   隨機   

一、ThreadLocal簡介

ThreadLocal是線程的局部變數,是每一個線程所單獨持有的,其他線程不能對其進行訪問,通常是類中的private static欄位。

我們知道有時候一個對象的變數會被多個線程所訪問,這時就會有安全執行緒問題,當然我們可以使用synchorinized 關鍵字來為此變數加鎖,進行同步處理,從而限制只能有一個線程來使用此變數,但是加鎖會大大影響程式執行效率,此外我們還可以使用ThreadLocal來解決對某一個變數的存取違規問題。

當使用ThreadLocal維護變數的時候 為每一個使用該變數的線程提供一個獨立的變數副本,即每個線程內部都會有一個該變數,這樣同時多個線程訪問該變數並不會彼此相互影響,因此他們使用的都是自己從記憶體中拷貝過來的變數的副本, 這樣就不存線上程安全問題,也不會影響程式的執行效能。

但是要注意,雖然ThreadLocal能夠解決上面說的問題,但是由於在每個線程中都建立了副本,所以要考慮它對資源的消耗,比如記憶體的佔用會比不使用ThreadLocal要大。

二、ThreadLocal源碼分析(1)ThreadLocal方法

ThreadLocal 的幾個方法: ThreadLocal 可以儲存任何類型的變數對象, get返回的是一個Object對象,但是我們可以通過泛型來制定儲存物件的類型。

public T get() { } // 用來擷取ThreadLocal在當前線程中儲存的變數副本public void set(T value) { } //set()用來設定當前線程中變數的副本public void remove() { } //remove()用來移除當前線程中變數的副本protected T initialValue() { } //initialValue()是一個protected方法,一般是用來在使用時進行重寫的
(2)ThreadLocal實現原理1.內部結構

Thread 在內部是通過ThreadLocalMap來維護ThreadLocal變數表, 在Thread類中有一個threadLocals 變數,是ThreadLocalMap類型的,它就是為每一個線程來儲存自身的ThreadLocal變數的。

ThreadLocal.ThreadLocalMap threadLocals = null;

 ThreadLocalMap 是定義在ThreadLocal 類裡的內部類,它的作用是儲存線程的局部變數。ThreadLocalMap 以ThreadLocal的引用作為鍵,以局部變數作為值,儲存在ThreadLocalMap.Entry (一種儲存索引值的資料結構)裡。這是因為在每一個線程裡面,可能存在著多個ThreadLocal變數

static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> {        /** The value associated with this ThreadLocal. */        Object value;        Entry(ThreadLocal<?> k, Object v) {            super(k);            value = v;        }    }}
2.調用過程

初始時,在Thread裡面,threadLocals為空白,當通過ThreadLocal變數調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,並且以當前ThreadLocal變數為索引值,以ThreadLocal要儲存的副本變數為value,存到threadLocals。
然後在當前線程裡面,如果要使用副本變數,就可以通過get方法在threadLocals裡面尋找

注意:

在進行get之前,必須先set,否則會報null 指標異常;

如果想在get之前不需要調用set就能正常訪問的話,必須重寫initialValue()方法。
  

ThreadLocal的set(T value)方法
public void set(T value) {  // 獲得當前線程Thread t = Thread.currentThread();  // 獲得當前線程的 ThreadLocalMap 引用,詳細見下ThreadLocalMap map = getMap(t);  // 如果不為空白,則更新局部變數的值if (map != null)  map.set(this, value);  //如果不是第一次使用,先進行初始化else  createMap(t, value);}
內部類ThreadLocalMap的set(ThreadLocal<?> key,Object value)
    private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length;    // Hash 定址,與table數組長度減1(二進位全是1)相與,所以數組長度必須為2的次方,減小hash重複的可能性 int i = key.threadLocalHashCode & (len-1);   //從hash值計算出的下標開始遍曆 for (Entry e = tab[i];      e != null;      e = tab[i = nextIndex(i, len)]) {   //獲得該Entry的鍵   ThreadLocal<?> k = e.get();    //如果鍵和傳過來的相同,覆蓋原值,也說明,一個ThreadLocal變數只能為一個線程儲存一個局部變數   if (k == key) {     e.value = value;     return;   }   // 鍵為空白,則替換該節點   if (k == null) {     replaceStaleEntry(key, value, i);     return;   } } tab[i] = new Entry(key, value); int sz = ++size;   //是否需要擴容 if (!cleanSomeSlots(i, sz) && sz >= threshold)   rehash();}

可以看出ThreadLocalMap 採用線性探測再散列解決Hash衝突的問題。即,如果一次Hash計算出來的數組下標被佔用,即hash值重複了,則在該下標的基礎上加1測試下一個下標,直到找到空值。比如說,Hash計算出來下標i為6,table[6] 已經有值了,那麼就嘗試table[7]是否被佔用,依次類推,直到找到空值。以上,就是儲存執行緒區域變數的方法。

TheadLocal的get()方法
public T get() {  //獲得當前線程    Thread t = Thread.currentThread();  //得到當前線程的一個threadLocals 變數ThreadLocalMap map = getMap(t);if (map != null) {  // 如果不為空白,以當前ThreadLocal為主鍵獲得對應的Entry  ThreadLocalMap.Entry e = map.getEntry(this);  if (e != null) {    @SuppressWarnings("unchecked")    T result = (T)e.value;    return result;  }}  //如果值為空白,則進行初始化return setInitialValue();}
ThreadLocal的setInitialValue()方法
private T setInitialValue() {  //獲得初始預設值T value = initialValue();  //得到當前線程Thread t = Thread.currentThread();  // 獲得該線程的ThreadLocalMap引用ThreadLocalMap map = getMap(t);  //不為空白則覆蓋if (map != null)    map.set(this, value);else      //若是為空白,則進行初始化,鍵為本ThreadLocal變數,值為預設值    createMap(t, value);}// 預設初始化返回null值,這也是 下面demo 為什麼需要重寫該方法的原因。如果沒有重寫,第一次get()操作獲得的執行緒區域變數為null,需要進行判斷並手動調用set()進行初始化protected T initialValue() {    return null;}
三、Demo

下面是個ThreadLocal使用的執行個體,兩個任務共用同一個變數,並且兩個任務都把該變數設定為了線程私人變數,這樣,雖然兩個任務都”持有“同一變數,但各自持有該變數的拷貝。因此,當一個線程修改該變數時,不會影響另一線程該變數的值。

package thread.ThreadLocalTest;import java.util.Random;import java.util.concurrent.TimeUnit;/*** Created by StoneGeek on 2018/8/1.* 部落格地址:http://www.cnblogs.com/sxkgeek*/public class ThreadLocalDemo2 implements Runnable {    // 一般會把 ThreadLocal 設定為static 。它只是個為線程設定局部變數的入口,多個線程只需要一個入口    private static ThreadLocal<Student> localStudent = new ThreadLocal() {    // 一般會重寫初始化方法,一會分析源碼時候會解釋為什麼        @Override        public Student initialValue() {            return new Student();    }};private Student student = null;@Overridepublic void run() {    String threadName = Thread.currentThread().getName();    System.out.println("【" + threadName + "】:is running !");    Random ramdom = new Random();    //隨機產生一個變數    int age = ramdom.nextInt(100);    System.out.println("【" + threadName + "】:set age to :" + age);    // 獲得線程局部變數,改變屬性值    Student stu = getStudent();    stu.setAge(age);    System.out.println("【" + threadName + "】:第一次讀到的age值為 :" + stu.getAge());    try {        TimeUnit.SECONDS.sleep(2);    } catch (InterruptedException e) {        e.printStackTrace();    }    System.out.println("【" + threadName + "】:第二次讀到的age值為 :" + stu.getAge());}public Student getStudent() {    student = localStudent.get();//         如果不重寫初始化方法,則需要判斷是否為空白,然後手動為ThreadLocal賦值,否則的話會報null 指標異常//        if(student == null){//            student = new Student();//            localStudent.set(student);//        }    return student;}public static void main(String[] args) {    ThreadLocalDemo2 ll = new ThreadLocalDemo2();    Thread t1 = new Thread(ll, "線程1");    Thread t2 = new Thread(ll, "線程2");    t1.start();    t2.start();}}console列印:【線程2】:is running !【線程1】:is running !【線程1】:set age to :67【線程2】:set age to :4【線程1】:第一次讀到的age值為 :67【線程2】:第一次讀到的age值為 :4【線程1】:第二次讀到的age值為 :67【線程2】:第二次讀到的age值為 :4
四、應用情境

最常見的ThreadLocal使用情境為
用來解決 資料庫連接、Session管理等。

資料庫連接
Class A implements Runnable{private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {    public Connection initialValue() {            return DriverManager.getConnection(DB_URL);    }};public static Connection getConnection() {       return connectionHolder.get();}}
Session管理
private static final ThreadLocal threadSession = new ThreadLocal();public static Session getSession() throws InfrastructureException {    Session s = (Session) threadSession.get();    try {        if (s == null) {            s = getSessionFactory().openSession();            threadSession.set(s);        }    } catch (HibernateException ex) {        throw new InfrastructureException(ex);    }    return s;}

java之ThreadLocal詳解

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.