ThreadLocal源碼閱讀

前言

本篇要說的是ThreadLocal,這個玩意平時在項目中很少用到,但是卻有極大的用處;平時在面試中也會經常問到這個問題。

正文

本篇使用jdk1.8版本。

ThreadLocal介紹

先來看看源碼中的介紹吧,文檔太多,就不全貼出來了

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.

使用方式

在註釋中也寫明瞭使用方法:

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadId {
      // Atomic integer containing the next thread ID to be assigned
      private static final AtomicInteger nextId = new AtomicInteger(0);
 
      // Thread local variable containing each thread's ID
      private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
              @Override 
              protected Integer initialValue() {
                  return nextId.getAndIncrement();
          }
      };
 
      // Returns the current thread's unique ID, assigning it if necessary
      
      public static int get() {
          return threadId.get();
      }
  }

概括下大概吧:

  1. ThreadLocal提供一個線程局部變量
  2. 每個線程只能通過該類的set與get方法獲取一個變量
  3. 獨立初始化變量
  4. 生命週期跟線程的生命週期相同

源碼

ThreadLocal中的源碼:

首先來看set方法:


public void set(T value) {
        // 獲取當前線程對象(所以多線程的情況下都是操作的當前線程)
        Thread t = Thread.currentThread();
        
        // 從當前線程對象中獲取ThreadLocalMap對象
        ThreadLocalMap map = getMap(t);
        
        // 如果ThreadLocalMap不爲null,直接存入
        if (map != null)
            // key爲當前Threadlocal實例
            map.set(this, value);
        else
        //如果ThreadLocalMap爲null,則爲當前線程創建ThreadLocalMap對象
            createMap(t, value);
}

接下來在看看get方法:


public T get() {
        //    獲取當前線程對象
        Thread t = Thread.currentThread();
        
        //    獲取當前線程對象的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        
        if (map != null) {
        // 以當前ThreadLocal實例爲key獲取map中的Entry對象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    

再來看看上面兩個方法中使用到的方法:

  1. 第一個,getMap方法:

// 多個方法都會調用這個方法
ThreadLocalMap getMap(Thread t) {
        // 返回傳入線程的ThreadLocalMap
        return t.threadLocals;
}

  1. createMap方法:

void createMap(Thread t, T firstValue) {
        // 爲線程threadLocals賦值(新建)
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

  1. setInitialValue方法

private T setInitialValue() {
        // 調用initialValue方法,獲取初始化值
        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. initialValue方法:

protected T initialValue() {
        return null;
    }

小結1: 如果在初始化的時候沒用重寫這個方法,將會返回null,所以這個方法一般會在初始化ThreadLocal的代碼中重寫這個方法。

在看看Thread中的代碼:


  /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    

小結2:
從上面的代碼中可以發現,set方法通過Thread.currentThread()來獲取當前線程對象,從而操作當前線程對象中的threadLocals,也就是說通過Threadlocal操作的數據是Thread負責維護的;而threadLocals又是ThreadLocal類中的一個靜態內部類ThreadLocalMap,其中keythis(這裏就是當前的Threadlocal實例對象);

小結3:

  • 從源碼上看出,不能從解決多線程程序的併發問題解決多線程訪問資源時的共享問題這兩個方面去看待Threadlocal的問題。
  • 我對Threadlocal的大致理解就是:通過Threadlocal的實例對象a去操作當前ThreadThreadLocal.ThreadLocalMap對象中keya所對應的數據;

使用場景

1. 數據庫連接池

最典型的應該就是數據庫連接池了吧,如何處理線程與數據庫連接之間的關係,應該就是用到的Threadlocal,具體的就得去谷歌或去看源碼了。

2. 信息或參數傳遞

這裏說一下我所用到過的場景:

  1. 信息傳遞
    有些接口中不會將某些信息作爲參數進行傳遞,但是在這樣的情況下卻需要某些信息,而這些信息只在最開始的時候能獲取到,這時候就可以使用ThreadLocal來操作了。

內存泄漏

關於內存泄漏,其實我在使用過程中也遇到過一次;下面說一下場景吧:

下面是流程圖:

流程圖

我們使用ThreadLocal來對用戶信息進行記錄,對請求進行攔截,將用戶信息存入,但是在有時候會出現遊客模式下回顯示已登錄,還不是自己的賬戶;

爲什麼會出現這種情況呢?

  1. 請求a(帶用戶信息a)在請求連接池獲取線程A
  2. 攔截器對線程A進行攔截,並對用戶信息a保存Threadlocal
  3. 然後在後臺處理中處理A對線程A進行處理,從Threadlocal中獲取用戶信息a
  4. 處理完成後,線程A帶着用戶信息a又回到了請求連接池,Threadlocal的用戶信息a也並未清除,且線程A並未被回收
  5. 請求b(未帶用戶信息)又在請求連接池獲取了線程A,這時未獲取用戶信息,也未對Threadlocal進行操作
  6. 回到步驟2

解決辦法:

  1. 攔截器處每次都對Threadlocal進行信息清除(調用Threadlocal的remove方法)
  2. 即便用戶信息爲null,也進行保存;

最後

博客地址:

項目GitHub地址:

參考文章:
ThreadLocal就是這麼簡單
Java ThreadLocal
Java進階(七)正確理解Thread Local的原理與適用場景

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