<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java多線程之ThreadLocal

          共 10773字,需瀏覽 22分鐘

           ·

          2021-09-06 21:56

          本文介紹下Java多線程方面高頻出現(xiàn)的ThreadLocal類

          abstract.jpeg

          基本實(shí)踐

          在傳統(tǒng)的多線程開(kāi)發(fā)場(chǎng)景中,為了避免多個(gè)線程同時(shí)操作一個(gè)共享變量而引起并發(fā)的問(wèn)題。通常會(huì)通過(guò)加鎖的形式進(jìn)行處理。特別是在這個(gè)共享變量,并不是一個(gè)所謂的共享資源而只是用于線程內(nèi)部各方法傳遞、使用的參數(shù)時(shí),這種加鎖的并發(fā)控制顯然會(huì)降低系統(tǒng)的吞吐量。而ThreadLocal類則給我們提供一個(gè)新的思路——線程本地私有存儲(chǔ)數(shù)據(jù)。簡(jiǎn)單來(lái)說(shuō)就是,ThreadLocal為共享變量在每個(gè)線程內(nèi)部提供了一個(gè)副本,用于進(jìn)行線程內(nèi)部自身的訪問(wèn)、存儲(chǔ)等操作。避免了該共享變量在多線程環(huán)境下的操作沖突。該類典型方法如下所示

          // 設(shè)置Value
          public void set(T value);

          // 獲取數(shù)據(jù)
          public T get();

          // 移除數(shù)據(jù)
          public void remove();

          這里通過(guò)一個(gè)Demo來(lái)了解下該如何使用

          package com.aaron.ThreadLocalTest;

          import java.util.Random;

          public class Demo1 {

              public static void main(String[] args) throws Exception{
                  Runnable task = new MyTask();

                  new Thread( task ).start();
                  new Thread( task ).start();

              }

          }

          class MyTask implements Runnable {

              private Integer num = null;

              private ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

              @Override
              public void run() {
                  // 每個(gè)線程隨機(jī)生產(chǎn)一個(gè)數(shù)
                  Integer count = new Random().nextInt(100);
                  System.out.println(Thread.currentThread().getName() + ", count: " + count );

                  // 模擬業(yè)務(wù)耗時(shí)
                  try{
                      Thread.sleep(5000);
                  }catch (Exception e) {
                  }

                  // 存儲(chǔ)數(shù)據(jù)
                  num = count;
                  threadLocal.set(count);

                  // 獲取數(shù)據(jù)
                  System.out.println( Thread.currentThread().getName() + ", num: " + num + ", threadLocal: " +threadLocal.get() );

                  // 移除當(dāng)前線程所存的數(shù)據(jù)
                  threadLocal.remove();
              }
          }

          通過(guò)上述示例及其測(cè)試結(jié)果,可以看出對(duì)于普通的共享變量num而言,在多線程操作過(guò)程中會(huì)發(fā)生沖突。具體表現(xiàn)為T(mén)hread-0線程下該變量本來(lái)為53,卻又被Thread-1線程修改為74;而對(duì)于threadLocal變量而言,從測(cè)試結(jié)果直觀上我們就可看出并未在多線程環(huán)境下發(fā)生沖突,各線程的threadLocal變量數(shù)據(jù)被線程隔離了

          figure 1.jpeg

          實(shí)現(xiàn)原理

          前面我們提到ThreadLocal是通過(guò)線程本地私有存儲(chǔ)數(shù)據(jù)實(shí)現(xiàn)線程安全的。這里結(jié)合ThreadLocal、Thread類的源碼做進(jìn)一步闡述。以set方法為例,其首先通過(guò)currentThread獲取當(dāng)前線程的Thread實(shí)例。并通過(guò)getMap方法獲取該線程的threadLocals屬性,即一個(gè)ThreadLocal.ThreadLocalMap實(shí)例。而在ThreadLocalMap則通過(guò)Entry實(shí)現(xiàn)對(duì)ThreadLocal及其值的存儲(chǔ)。進(jìn)一步地,為了支持存儲(chǔ)多個(gè)ThreadLocal變量及其值,ThreadLocalMap類中提供一個(gè)Entry數(shù)組類型的table字段。get方法同理,不再贅述。簡(jiǎn)而言之,ThreadLocal之所以可以實(shí)現(xiàn)對(duì)數(shù)據(jù)的線程隔離,是因?yàn)槠鋵?shù)據(jù)存儲(chǔ)到Thread實(shí)例中

          public class ThreadLocal<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);
                  }
              }

              public T get() {
                  Thread t = Thread.currentThread();
                  ThreadLocalMap map = getMap(t);
                  if (map != null) {
                      ThreadLocalMap.Entry e = map.getEntry(this);
                      if (e != null) {
                          @SuppressWarnings("unchecked")
                          T result = (T)e.value;
                          return result;
                      }
                  }
                  return setInitialValue();
              }

              ThreadLocalMap getMap(Thread t) {
                  return t.threadLocals;
              }

              void createMap(Thread t, T firstValue) {
                  t.threadLocals = new ThreadLocalMap(this, firstValue);
              }

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

                  private Entry[] table;

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

          }

          ...

          public class Thread implements Runnable {

              ThreadLocal.ThreadLocalMap threadLocals = null;

          }

          應(yīng)用場(chǎng)景

          線程內(nèi)資源的復(fù)用

          眾所周知,時(shí)間格式化類SimpleDateFormat在多線程環(huán)境下是非線程安全的。為此傳統(tǒng)的解決方案,要么通過(guò)加鎖的方式實(shí)現(xiàn),此舉會(huì)產(chǎn)生阻塞顯著降低效率;要么則定義為局部變量,每次使用需自行new該實(shí)例,如果任務(wù)的數(shù)量較多,顯然會(huì)嚴(yán)重浪費(fèi)內(nèi)存、CPU等資源。而ThreadLocal則可以很好的解決該問(wèn)題。將SimpleDateFormat實(shí)例與線程實(shí)例進(jìn)行綁定。一方面,各線程使用不同的SimpleDateFormat實(shí)例,避免了SimpleDateFormat線程不安全問(wèn)題;另一方面,在基于線程池的方式利用線程的場(chǎng)景下,該線程所綁定的SimpleDateFormat實(shí)例在本次任務(wù)完成后,可以在該線程下一次的任務(wù)中繼續(xù)復(fù)用。避免根據(jù)任務(wù)頻繁地創(chuàng)建SimpleDateFormat實(shí)例。示例Demo如下所示

          public class Demo2 {

              public static void main(String[] args) throws Exception{
                  Runnable task = new Task();

                  new Thread( task ).start();
                  new Thread( task ).start();
              }

          }

          class Task implements Runnable {

              private SimpleDateFormat dateFormat1 = new SimpleDateFormat("HH:mm:ss");

             // 使用static進(jìn)行修飾,保持對(duì)ThreadLocal實(shí)例的強(qiáng)引用。這樣只要該線程不結(jié)束退出,該SimpleDateFormat即可通過(guò)dateFormat2重復(fù)訪問(wèn)、使用
              private static ThreadLocal<SimpleDateFormat> dateFormat2 = ThreadLocal.withInitial(
                  () -> new SimpleDateFormat("HH:mm:ss")
              );

              @Override
              public void run() {
                  Long ts = RandomUtil.randomLong(10L5000000L);
                  Date date = new Date(ts);
                  System.out.println(Thread.currentThread().getName() + ", date: " + date );

                  String str1 = dateFormat1.format(date);
                  String str2 = dateFormat2.get().format(date);

                  System.out.println( Thread.currentThread().getName() + ", str1: " + str1 + ", str2: " + str2 );

              }

          }

          測(cè)試結(jié)果如下所示,兩個(gè)線程str1輸出的結(jié)果證明了SimpleDateFormat的非線程安全

          figure 2.jpeg

          傳遞上下文

          鑒于ThreadLocal的線程隔離特性,可以很方便我們?cè)谝粋€(gè)線程內(nèi)的多個(gè)方法進(jìn)行參數(shù)傳遞。即所謂的Context上下文。典型地,包括用戶身份信息、數(shù)據(jù)庫(kù)連接信息等。以避免通過(guò)添加入?yún)⒌男问竭M(jìn)行傳遞

          內(nèi)存泄露

          Entry的Key

          前面我們提到在ThreadLocalMap內(nèi)部是通過(guò)Entry實(shí)現(xiàn)對(duì)ThreadLocal及其值的存儲(chǔ)。而Entry的key字段則是一個(gè)指向ThreadLocal實(shí)例的弱引用。這里對(duì)弱引用 WeakReference 作必要的補(bǔ)充說(shuō)明:GC進(jìn)行回收時(shí),對(duì)于只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間是否充足,均會(huì)回收該對(duì)象。故當(dāng)一個(gè)ThreadLocal實(shí)例沒(méi)有外部強(qiáng)引用時(shí),其必然可以被GC回收,顯然利用static修飾的ThreadLocal變量除外。試想如果Entry的key字段是一個(gè)指向ThreadLocal實(shí)例的強(qiáng)引用,那么如果該線程永遠(yuǎn)不結(jié)束退出,則會(huì)導(dǎo)致ThreadLocal實(shí)例無(wú)法被回收

          Entry的Value

          需要注意的是,Entry存儲(chǔ)value則是通過(guò)強(qiáng)引用進(jìn)行關(guān)聯(lián)的。結(jié)合前面通過(guò)對(duì)Entry的key進(jìn)行分析可知,一旦ThreadLocal實(shí)例不存在外部的強(qiáng)引用而被GC回收后,則相應(yīng)的Entry實(shí)例就會(huì)變?yōu)閗ey為null而value依然存在強(qiáng)引用。除非該線程退出結(jié)束,否則該value對(duì)象將會(huì)一直被Entry實(shí)例強(qiáng)引用而無(wú)法進(jìn)行回收。這也是大家通常所說(shuō)的ThreadLocal存在內(nèi)存泄露的根源所在。其實(shí),在ThreadLocal的set、get方法內(nèi)部實(shí)現(xiàn)中也會(huì)對(duì)Entry數(shù)組進(jìn)行檢查,對(duì)key為null的Entry實(shí)例進(jìn)行清除。但顯然這種清除的觸發(fā)是有一定的條件。故推薦大家在使用完ThreadLocal后,通過(guò)remove方法顯式地清除該value值。或者將ThreadLocal變量修飾為static屬性,以保持對(duì)ThreadLocal實(shí)例的強(qiáng)引用。這樣就能保證任何時(shí)候均可以通過(guò)ThreadLocal實(shí)例對(duì)value進(jìn)行更新、清除等操作

          瀏覽 87
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  可以直接看的av 免费性爱视频网站 | 高清一区二区三区日本久 | 日批网站在线看 | 大学生特黄特色大片免费祝频 | 三级片在线网站 |