溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中的ThreadLocal怎么用

發(fā)布時間:2021-12-04 14:17:40 來源:億速云 閱讀:177 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)Java中的ThreadLocal怎么用的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

引言

ThreadLocal的官方API解釋為:

“該類提供了線程局部 (thread-local)  變量。這些變量不同于它們的普通對應(yīng)物,因為訪問某個變量(通過其 get 或 set 方法)的每個線程都有自己的局部變量,它獨立于變量的初始化副 本。ThreadLocal 實例通常是類中的 private static 字段,它們希望將狀態(tài)與某一個線程(例如,用戶 ID 或事務(wù)  ID)相關(guān)聯(lián)?!?/p>

Java中的ThreadLocal怎么用

大概的意思有兩點:

  1. ThreadLocal提供了一種訪問某個變量的特殊方式:訪問到的變量屬于當(dāng)前線程,即保證每個線程的變量不一樣,而同一個線程在任何地方拿到的變量都是一致的,這就是所謂的線程隔離。

  2. 如果要使用ThreadLocal,通常定義為private static類型,在我看來***是定義為private static final類型。

應(yīng)用場景

ThreadLocal通常用來共享數(shù)據(jù),當(dāng)你想在多個方法中使用某個變量,這個變量是當(dāng)前線程的狀態(tài),其它線程不依賴這個變量,你***時間想到的 就是把變量定義在方法內(nèi)部,然后再方法之間傳遞參數(shù)來使用,這個方法能解決問題,但是有個煩人的地方就是,每個方法都需要聲明形參,多處聲明,多處調(diào)用。 影響代碼的美觀和維護。有沒有一種方法能將變量像private  static形式來訪問呢?這樣在類的任何一處地方就都能使用。這個時候ThreadLocal大顯身手了。

實踐

我們首先來看一段代碼:

mport java.util.HashMap;  import java.util.Map;  public class TreadLocalTest {  // static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>(){  // @Override  // protected HashMap initialValue() {  // System.out.println(Thread.currentThread().getName()+”initialValue”);  // return new HashMap();  // }  // };  public static class T1 implements Runnable {  private final static Map map = new HashMap();  int id;  public T1(int id) {  this.id = id;  }  public void run() {  // Map map = threadLocal.get();  for (int i = 0; i < 20; i++) {  map.put(i, i + id * 100);  try {  Thread.sleep(100);  } catch (Exception ex) {  }  }  System.out.println(Thread.currentThread().getName()  + “# map.size()=” + map.size() + ” # ” + map);  }  }  public static void main(String[] args) {  Thread[] runs = new Thread[15];  T1 t = new T1(1);  for (int i = 0; i < runs.length; i++) {  runs[i] = new Thread(t);  }  for (int i = 0; i < runs.length; i++) {  runs[i].start();  }  }  }

這段程序的本意是,啟動15個線程,線程向map中寫入20個整型值,然后輸出map。運行該程序,觀察結(jié)果,我們會發(fā)現(xiàn),map中壓根就不止20個元素,這說明程序產(chǎn)生了線程安全問題。

我們都知道HashMap是非線程安全的,程序啟動了15個線程,他們共享了同一個map,15個線程都往map寫對象,這勢必引起線程安全問題。

我們有兩種方法解決這個問題:

  1. 將map的聲明放到run方法中,這樣map就成了方法內(nèi)部變量,每個線程都有一份new HashMap(),無論多少個線程執(zhí)行run方法,都不會有線程安全問題。這個方法也正如應(yīng)用場景中提到的,如果有多處地方使用到map,傳值是個煩人的地方。

  2. 將HashMap換成Hashtable。用線程同步來解決問題,然而我們的程序只是想向一個map中寫入20個整型的KEY-VALUE而已,并不需要線程同步,同步勢必影響性能,得不償失。

  3. ThreadLocal提供另外一種解決方案,即在解決方案a上邊,將new  HashMap()得到的實例變量,綁定到當(dāng)前線程中。之后從任何地方,都可以通過ThreadLocal獲取到該變量。將程序中的注釋代碼恢復(fù),再將  private final static Map map = new HashMap();注釋掉,運行程序,結(jié)果就是我們想要的。

實現(xiàn)原理

程序調(diào)用了get()方法,我們來看一下該方法的源碼:

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();  }  getMap方法的源碼:  ThreadLocalMap getMap(Thread t) {  return t.threadLocals;  }

該方法返回的是當(dāng)前線程中的ThreadLocalMap實例。閱讀Thread的源碼我們發(fā)現(xiàn)Thread中有如下變量聲明:

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

我們暫時可以將ThreadLocalMap理解為一個類似Map的這么個類,之后再講解它。

get()方法的大致意思就是從當(dāng)前線程中拿到ThreadLocalMap的實例threadLocals,如果threadLocals不為 空,那么就以當(dāng)前ThreadLocal實例為KEY從threadLocals中拿到對應(yīng)的VALUE。如果不為空,那么就調(diào)用  setInitialValue()方法初始化threadLocals,最終返回的是initialValue()方法的返回值。下面是  setInitialValue()方法的源碼

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;  }

我們看到map.set(this, value);這句代碼將ThreadLocalMap的實例作為KEY,將initialValue()的返回值作為VALUE,set到了threadLocals中。

程序在聲明ThreadLocal實例的時候覆寫了initialValue(),返回了VALUE,當(dāng)然我們可以直接調(diào)用set(T t)方法來設(shè)置VALUE。下面是set(T t)方法的源碼:

public void set(T value) {  Thread t = Thread.currentThread();  ThreadLocalMap map = getMap(t);  if (map != null)  map.set(this, value);  else  createMap(t, value);  }

我們看到它比setInitialValue()方法就少了個return語句。這兩種方式都能達到初始化ThreadLocalMap實例的效果。

我們再來看一下ThreadLocal類的結(jié)構(gòu)。

ThreadLocal類只有三個屬性,如下:

/*ThreadLocal的hash值,map用它來存儲值*/

private final int threadLocalHashCode = nextHashCode();

/*改類能以原子的方式更新int值,這里主要是在產(chǎn)生新的ThreadLocal實例時用來產(chǎn)生一個新的hash值,map用該值來存儲對象*/

private static AtomicInteger nextHashCode = new AtomicInteger();

/*該變量標識每次產(chǎn)生新的ThreadLocal實例時,hash值的增量*/

private static final int HASH_INCREMENT = 0x61c88647;

剩下的就是一些方法。最關(guān)鍵的地方就是ThreadLocal定義了一個靜態(tài)內(nèi)部類ThreadLocalMap。我們在下一章節(jié)再來分析這個類。 從ThreadLocal的類結(jié)構(gòu),我們可以看到,實際上問題的關(guān)鍵先生是ThreadLocalMap,ThreadLocal只是提供了管理的功能, 我們也可以說ThreadLocal只是代理了ThreadLocalMap而已。

ThreadLocalMap源碼分析

既然ThreadLocalMap實現(xiàn)了類似map的功能,那我們首先來看看它的set方法源碼:

private void set(ThreadLocal key, Object value) {  // We don&rsquo;t use a fast path as with get() because it is at  // least as common to use set() to create new entries as  // it is to replace existing ones, in which case, a fast  // path would fail more often than not.  Entry[] tab = table;  int len = tab.length;  int i = key.threadLocalHashCode & (len-1);  for (Entry e = tab[i];  e != null;  e = tab[i = nextIndex(i, len)]) {  ThreadLocal k = e.get();  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();  }

這個方法的主要功能就是講KEY-VALUE存儲到ThreadLocalMap中,這里至少我們看到KEY實際上是  key.threadLocalHashCode,ThreadLocalMap同樣維護著Entry數(shù)組,這個Entry我們在下一節(jié)會講解。這里涉及 到了Hash沖突的處理,這里并不會向HashMap一樣沖突了以鏈表的形式往后添加。如果對這個Hash沖突解決方案有興趣,可以再進一步研究源碼。

既然ThreadLocalMap也是用Entry來存儲對象,那我們來看看Entry類的聲明,Entry被定義在ThreadLocalMap的內(nèi)部:

static class Entry extends WeakReference<ThreadLocal> {  /** The value associated with this ThreadLocal. */  Object value;  Entry(ThreadLocal k, Object v) {  super(k);  value = v;  }  }

這里我們看到Entry集成了WeakReference類,泛型聲明了ThreadLocal,即每一個Entry對象都保留了對  ThreadLocal實例的弱引用,之所以這么干的原因是,線程在結(jié)束之后需要將ThreadLocal實例從map中remove調(diào),以便回收內(nèi)存空 間。

總結(jié)

首先,ThreadLocalMap并不是為了解決線程安全問題,而是提供了一種將實例綁定到當(dāng)前線程的機制,類似于隔離的效果,實際上自己在方法 中new出來變量也能達到類似的效果。ThreadLocalMap跟線程安全基本不搭邊,綁定上去的實例也不是多線程公用的,而是每個線程new一份, 這個實例肯定不是共用的,如果共用了,那就會引發(fā)線程安全問題。ThreadLocalMap***的用處就是用來把實例變量共享成全局變量,在程序的任何 方法中都可以訪問到該實例變量而已。網(wǎng)上很多人說ThreadLocalMap是解決了線程安全問題,其實是望文生義,兩者不是同類問題。

感謝各位的閱讀!關(guān)于“Java中的ThreadLocal怎么用”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI