国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

Java ThreadLocal的使用場(chǎng)景總結

發(fā)布時(shí)間:2021-07-06 11:13 來(lái)源:腳本之家 閱讀:0 作者:Java中文社群 欄目: 開(kāi)發(fā)技術(shù) 歡迎投稿:712375056

目錄

      使用場(chǎng)景1:本地變量

      我們以多線(xiàn)程格式化時(shí)間為例,來(lái)演示 ThreadLocal 的價(jià)值和作用,當我們在多個(gè)線(xiàn)程中格式化時(shí)間時(shí),通常會(huì )這樣操作。

      ① 2個(gè)線(xiàn)程格式化

      當有 2 個(gè)線(xiàn)程進(jìn)行時(shí)間格式化時(shí),我們可以這樣寫(xiě):

      import java.text.SimpleDateFormat;
      import java.util.Date;
      
      public class Test {
          public static void main(String[] args) throws InterruptedException {
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程1
              Thread t1 = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      // 得到時(shí)間對象
                      Date date = new Date(1 * 1000);
                      // 執行時(shí)間格式化
                      formatAndPrint(date);
                  }
              });
              t1.start();
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程2
              Thread t2 = new Thread(new Runnable() {
                  @Override
                  public void run() {
                      // 得到時(shí)間對象
                      Date date = new Date(2 * 1000);
                      // 執行時(shí)間格式化
                      formatAndPrint(date);
                  }
              });
              t2.start();
          }
      
          /**
           * 格式化并打印結果
           * @param date 時(shí)間對象
           */
          private static void formatAndPrint(Date date) {
              // 格式化時(shí)間對象
              SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
              // 執行格式化
              String result = simpleDateFormat.format(date);
              // 打印最終結果
              System.out.println("時(shí)間:" + result);
          }
      }

      以上程序的執行結果為:

      上面的代碼因為創(chuàng )建的線(xiàn)程數量并不多,所以我們可以給每個(gè)線(xiàn)程創(chuàng )建一個(gè)私有對象 SimpleDateFormat 來(lái)進(jìn)行時(shí)間格式化。

      ② 10個(gè)線(xiàn)程格式化

      當線(xiàn)程的數量從 2 個(gè)升級為 10 個(gè)時(shí),我們可以使用 for 循環(huán)來(lái)創(chuàng )建多個(gè)線(xiàn)程執行時(shí)間格式化,具體實(shí)現代碼如下:

      import java.text.SimpleDateFormat;
      import java.util.Date;
      
      public class Test {
          public static void main(String[] args) throws InterruptedException {
              for (int i = 0; i < 10; i++) {
                  int finalI = i;
                  // 創(chuàng  )建線(xiàn)程
                  Thread thread = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          // 得到時(shí)間對象
                          Date date = new Date(finalI * 1000);
                          // 執行時(shí)間格式化
                          formatAndPrint(date);
                      }
                  });
                  // 啟動(dòng)線(xiàn)程
                  thread.start();
              }
          }
          /**
           * 格式化并打印時(shí)間
           * @param date 時(shí)間對象
           */
          private static void formatAndPrint(Date date) {
              // 格式化時(shí)間對象
              SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
              // 執行格式化
              String result = simpleDateFormat.format(date);
              // 打印最終結果
              System.out.println("時(shí)間:" + result);
          }
      }

      以上程序的執行結果為:

      從上述結果可以看出,雖然此時(shí)創(chuàng )建的線(xiàn)程數和 SimpleDateFormat 的數量不算少,但程序還是可以正常運行的。

      ③ 1000個(gè)線(xiàn)程格式化

      然而當我們將線(xiàn)程的數量從 10 個(gè)變成 1000 個(gè)的時(shí)候,我們就不能單純的使用 for 循環(huán)來(lái)創(chuàng )建 1000 個(gè)線(xiàn)程的方式來(lái)解決問(wèn)題了,因為這樣頻繁的新建和銷(xiāo)毀線(xiàn)程會(huì )造成大量的系統開(kāi)銷(xiāo)和線(xiàn)程過(guò)度爭搶 CPU 資源的問(wèn)題。

      所以經(jīng)過(guò)一番思考后,我們決定使用線(xiàn)程池來(lái)執行這 1000 次的任務(wù),因為線(xiàn)程池可以復用線(xiàn)程資源,無(wú)需頻繁的新建和銷(xiāo)毀線(xiàn)程,也可以通過(guò)控制線(xiàn)程池中線(xiàn)程的數量來(lái)避免過(guò)多線(xiàn)程所導致的 **CPU** 資源過(guò)度爭搶和線(xiàn)程頻繁切換所造成的性能問(wèn)題,而且我們可以將 SimpleDateFormat 提升為全局變量,從而避免每次執行都要新建 SimpleDateFormat 的問(wèn)題,于是我們寫(xiě)下了這樣的代碼:

      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.concurrent.LinkedBlockingQueue;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      
      public class App {
          // 時(shí)間格式化對象
          private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
      
          public static void main(String[] args) throws InterruptedException {
              // 創(chuàng  )建線(xiàn)程池執行任務(wù)
              ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
                      TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
              for (int i = 0; i < 1000; i++) {
                  int finalI = i;
                  // 執行任務(wù)
                  threadPool.execute(new Runnable() {
                      @Override
                      public void run() {
                          // 得到時(shí)間對象
                          Date date = new Date(finalI * 1000);
                          // 執行時(shí)間格式化
                          formatAndPrint(date);
                      }
                  });
              }
              // 線(xiàn)程池執行完任務(wù)之后關(guān)閉
              threadPool.shutdown();
          }
      
          /**
           * 格式化并打印時(shí)間
           * @param date 時(shí)間對象
           */
          private static void formatAndPrint(Date date) {
              // 執行格式化
              String result = simpleDateFormat.format(date);
              // 打印最終結果
              System.out.println("時(shí)間:" + result);
          }
      }

      以上程序的執行結果為:

      當我們懷著(zhù)無(wú)比喜悅的心情去運行程序的時(shí)候,卻發(fā)現意外發(fā)生了,這樣寫(xiě)代碼竟然會(huì )出現線(xiàn)程安全的問(wèn)題。從上述結果可以看出,程序的打印結果竟然有重復內容的,正確的情況應該是沒(méi)有重復的時(shí)間才對。

      PS:所謂的線(xiàn)程安全問(wèn)題是指:在多線(xiàn)程的執行中,程序的執行結果與預期結果不相符的情況。

      a) 線(xiàn)程安全問(wèn)題分析

      為了找到問(wèn)題所在,我們嘗試查看 SimpleDateFormat 中 format 方法的源碼來(lái)排查一下問(wèn)題,format 源碼如下:

      private StringBuffer format(Date date, StringBuffer toAppendTo,
                                      FieldDelegate delegate) {
          // 注意此行代碼
          calendar.setTime(date);
      
          boolean useDateFormatSymbols = useDateFormatSymbols();
      
          for (int i = 0; i < compiledPattern.length; ) {
              int tag = compiledPattern[i] >>> 8;
              int count = compiledPattern[i++] & 0xff;
              if (count == 255) {
                  count = compiledPattern[i++] << 16;
                  count |= compiledPattern[i++];
              }
      
              switch (tag) {
                  case TAG_QUOTE_ASCII_CHAR:
                      toAppendTo.append((char)count);
                      break;
      
                  case TAG_QUOTE_CHARS:
                      toAppendTo.append(compiledPattern, i, count);
                      i += count;
                      break;
      
                  default:
                      subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                      break;
              }
          }
          return toAppendTo;
      }

      從上述源碼可以看出,在執行 SimpleDateFormat.format 方法時(shí),會(huì )使用 calendar.setTime 方法將輸入的時(shí)間進(jìn)行轉換,那么我們想象一下這樣的場(chǎng)景:

      1. 線(xiàn)程 1 執行了 calendar.setTime(date) 方法,將用戶(hù)輸入的時(shí)間轉換成了后面格式化時(shí)所需要的時(shí)間;
      2. 線(xiàn)程 1 暫停執行,線(xiàn)程 2 得到 CPU 時(shí)間片開(kāi)始執行;
      3. 線(xiàn)程 2 執行了 calendar.setTime(date) 方法,對時(shí)間進(jìn)行了修改;
      4. 線(xiàn)程 2 暫停執行,線(xiàn)程 1 得出 CPU 時(shí)間片繼續執行,因為線(xiàn)程 1 和線(xiàn)程 2 使用的是同一對象,而時(shí)間已經(jīng)被線(xiàn)程 2 修改了,所以此時(shí)當線(xiàn)程 1 繼續執行的時(shí)候就會(huì )出現線(xiàn)程安全的問(wèn)題了。

      正常的情況下,程序的執行是這樣的:

      非線(xiàn)程安全的執行流程是這樣的:

      b) 解決線(xiàn)程安全問(wèn)題:加鎖

      當出現線(xiàn)程安全問(wèn)題時(shí),我們想到的第一解決方案就是加鎖,具體的實(shí)現代碼如下:

      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.concurrent.LinkedBlockingQueue;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      
      public class App {
          // 時(shí)間格式化對象
          private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
      
          public static void main(String[] args) throws InterruptedException {
              // 創(chuàng  )建線(xiàn)程池執行任務(wù)
              ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
                      TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
              for (int i = 0; i < 1000; i++) {
                  int finalI = i;
                  // 執行任務(wù)
                  threadPool.execute(new Runnable() {
                      @Override
                      public void run() {
                          // 得到時(shí)間對象
                          Date date = new Date(finalI * 1000);
                          // 執行時(shí)間格式化
                          formatAndPrint(date);
                      }
                  });
              }
              // 線(xiàn)程池執行完任務(wù)之后關(guān)閉
              threadPool.shutdown();
          }
      
          /**
           * 格式化并打印時(shí)間
           * @param date 時(shí)間對象
           */
          private static void formatAndPrint(Date date) {
              // 執行格式化
              String result = null;
              // 加鎖
              synchronized (App.class) {
                  result = simpleDateFormat.format(date);
              }
              // 打印最終結果
              System.out.println("時(shí)間:" + result);
          }
      }

      以上程序的執行結果為:

      從上述結果可以看出,使用了 synchronized 加鎖之后程序就可以正常的執行了。

      加鎖的缺點(diǎn)

      加鎖的方式雖然可以解決線(xiàn)程安全的問(wèn)題,但同時(shí)也帶來(lái)了新的問(wèn)題,當程序加鎖之后,所有的線(xiàn)程必須排隊執行某些業(yè)務(wù)才行,這樣無(wú)形中就降低了程序的運行效率了。

      有沒(méi)有既能解決線(xiàn)程安全問(wèn)題,又能提高程序的執行速度的解決方案呢?

      答案是:有的,這個(gè)時(shí)候 ThreadLocal 就要上場(chǎng)了。

      c) 解決線(xiàn)程安全問(wèn)題:ThreadLocal

      1.ThreadLocal 介紹

      ThreadLocal 從字面的意思來(lái)理解是線(xiàn)程本地變量的意思,也就是說(shuō)它是線(xiàn)程中的私有變量,每個(gè)線(xiàn)程只能使用自己的變量。

      以上面線(xiàn)程池格式化時(shí)間為例,當線(xiàn)程池中有 10 個(gè)線(xiàn)程時(shí),SimpleDateFormat 會(huì )存入 ThreadLocal 中,它也只會(huì )創(chuàng )建 10 個(gè)對象,即使要執行 1000 次時(shí)間格式化任務(wù),依然只會(huì )新建 10 個(gè) SimpleDateFormat 對象,每個(gè)線(xiàn)程調用自己的 ThreadLocal 變量。

      2.ThreadLocal 基礎使用

      ThreadLocal 常用的核心方法有三個(gè):

      1. set 方法:用于設置線(xiàn)程獨立變量副本。 沒(méi)有 set 操作的 ThreadLocal 容易引起臟數據。
      2. get 方法:用于獲取線(xiàn)程獨立變量副本。 沒(méi)有 get 操作的 ThreadLocal 對象沒(méi)有意義。
      3. remove 方法:用于移除線(xiàn)程獨立變量副本。 沒(méi)有 remove 操作容易引起內存泄漏。

      ThreadLocal 所有方法如下圖所示:

      官方說(shuō)明文檔:

      ThreadLocal 基礎用法如下:

      /**
       * @公眾號:Java中文社群
       */
      public class ThreadLocalExample {
          // 創(chuàng  )建一個(gè) ThreadLocal 對象
          private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
      
          public static void main(String[] args) {
              // 線(xiàn)程執行任務(wù)
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      String threadName = Thread.currentThread().getName();
                      System.out.println(threadName + " 存入值:" + threadName);
                      // 在 ThreadLocal 中設置值
                      threadLocal.set(threadName);
                      // 執行方法,打印線(xiàn)程中設置的值
                      print(threadName);
                  }
              };
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 1
              new Thread(runnable, "MyThread-1").start();
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 2
              new Thread(runnable, "MyThread-2").start();
          }
      
          /**
           * 打印線(xiàn)程中的 ThreadLocal 值
           * @param threadName 線(xiàn)程名稱(chēng)
           */
          private static void print(String threadName) {
              try {
                  // 得到 ThreadLocal 中的值
                  String result = threadLocal.get();
                  // 打印結果
                  System.out.println(threadName + " 取出值:" + result);
              } finally {
                  // 移除 ThreadLocal 中的值(防止內存溢出)
                  threadLocal.remove();
              }
          }
      }

      以上程序的執行結果為:

      從上述結果可以看出,每個(gè)線(xiàn)程只會(huì )讀取到屬于自己的 ThreadLocal 值。

      3.ThreadLocal 高級用法

      ① 初始化:initialValue

      public class ThreadLocalByInitExample {
          // 定義 ThreadLocal
          private static ThreadLocal<String> threadLocal = new ThreadLocal(){
              @Override
              protected String initialValue() {
                  System.out.println("執行 initialValue() 方法");
                  return "默認值";
              }
          };
      
          public static void main(String[] args) {
              // 線(xiàn)程執行任務(wù)
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      // 執行方法,打印線(xiàn)程中數據(未設置值打?。?
                      print(threadName);
                  }
              };
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 1
              new Thread(runnable, "MyThread-1").start();
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 2
              new Thread(runnable, "MyThread-2").start();
          }
      
          /**
           * 打印線(xiàn)程中的 ThreadLocal 值
           * @param threadName 線(xiàn)程名稱(chēng)
           */
          private static void print(String threadName) {
              // 得到 ThreadLocal 中的值
              String result = threadLocal.get();
              // 打印結果
              System.out.println(threadName + " 得到值:" + result);
          }
      }

      以上程序的執行結果為:

      當使用了 #threadLocal.set 方法之后,initialValue 方法就不會(huì )被執行了,如下代碼所示:

      public class ThreadLocalByInitExample {
          // 定義 ThreadLocal
          private static ThreadLocal<String> threadLocal = new ThreadLocal() {
              @Override
              protected String initialValue() {
                  System.out.println("執行 initialValue() 方法");
                  return "默認值";
              }
          };
      
          public static void main(String[] args) {
              // 線(xiàn)程執行任務(wù)
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      String threadName = Thread.currentThread().getName();
                      System.out.println(threadName + " 存入值:" + threadName);
                      // 在 ThreadLocal 中設置值
                      threadLocal.set(threadName);
                      // 執行方法,打印線(xiàn)程中設置的值
                      print(threadName);
                  }
              };
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 1
              new Thread(runnable, "MyThread-1").start();
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 2
              new Thread(runnable, "MyThread-2").start();
          }
      
          /**
           * 打印線(xiàn)程中的 ThreadLocal 值
           * @param threadName 線(xiàn)程名稱(chēng)
           */
          private static void print(String threadName) {
              try {
                  // 得到 ThreadLocal 中的值
                  String result = threadLocal.get();
                  // 打印結果
                  System.out.println(threadName + "取出值:" + result);
              } finally {
                  // 移除 ThreadLocal 中的值(防止內存溢出)
                  threadLocal.remove();
              }
          }
      }

      以上程序的執行結果為:

      為什么 set 方法之后,初始化代碼就不執行了?
      要理解這個(gè)問(wèn)題,需要從 ThreadLocal.get() 方法的源碼中得到答案,因為初始化方法 initialValue 在 ThreadLocal 創(chuàng )建時(shí)并不會(huì )立即執行,而是在調用了 get 方法只會(huì )才會(huì )執行,測試代碼如下:

      import java.util.Date;
      
      public class ThreadLocalByInitExample {
          // 定義 ThreadLocal
          private static ThreadLocal<String> threadLocal = new ThreadLocal() {
              @Override
              protected String initialValue() {
                  System.out.println("執行 initialValue() 方法 " + new Date());
                  return "默認值";
              }
          };
          public static void main(String[] args) {
              // 線(xiàn)程執行任務(wù)
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      // 得到當前線(xiàn)程名稱(chēng)
                      String threadName = Thread.currentThread().getName();
                      // 執行方法,打印線(xiàn)程中設置的值
                      print(threadName);
                  }
              };
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 1
              new Thread(runnable, "MyThread-1").start();
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 2
              new Thread(runnable, "MyThread-2").start();
          }
      
          /**
           * 打印線(xiàn)程中的 ThreadLocal 值
           * @param threadName 線(xiàn)程名稱(chēng)
           */
          private static void print(String threadName) {
              System.out.println("進(jìn)入 print() 方法 " + new Date());
              try {
                  // 休眠 1s
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              // 得到 ThreadLocal 中的值
              String result = threadLocal.get();
              // 打印結果
              System.out.println(String.format("%s 取得值:%s %s",
                      threadName, result, new Date()));
          }
      }

      以上程序的執行結果為:

      從上述打印的時(shí)間可以看出:initialValue 方法并不是在 ThreadLocal 創(chuàng )建時(shí)執行的,而是在調用 Thread.get 方法時(shí)才執行的。

      接下來(lái)來(lái)看 Threadlocal.get 源碼的實(shí)現:

      public T get() {
          // 得到當前的線(xiàn)程
          Thread t = Thread.currentThread();
          ThreadLocalMap map = getMap(t);
          // 判斷 ThreadLocal 中是否有數據
          if (map != null) {
              ThreadLocalMap.Entry e = map.getEntry(this);
              if (e != null) {
                  @SuppressWarnings("unchecked")
                  T result = (T)e.value;
                  // 有 set 值,直接返回數據
                  return result;
              }
          }
          // 執行初始化方法【重點(diǎn)關(guān)注】
          return setInitialValue();
      }
      private T setInitialValue() {
          // 執行初始化方法【重點(diǎn)關(guān)注】
          T value = initialValue();
          Thread t = Thread.currentThread();
          ThreadLocalMap map = getMap(t);
          if (map != null)
              map.set(this, value);
          else
              createMap(t, value);
          return value;
      }

      從上述源碼可以看出,當 ThreadLocal 中有值時(shí)會(huì )直接返回值 e.value,只有 Threadlocal 中沒(méi)有任何值時(shí)才會(huì )執行初始化方法 initialValue。

      注意事項—類(lèi)型必須保持一致
      注意在使用 initialValue 時(shí),返回值的類(lèi)型要和 ThreadLoca 定義的數據類(lèi)型保持一致,如下圖所示:

      如果數據不一致就會(huì )造成 ClassCaseException 類(lèi)型轉換異常,如下圖所示:

      ② 初始化2:withInitial

      import java.util.function.Supplier;
      
      public class ThreadLocalByInitExample {
          // 定義 ThreadLocal
          private static ThreadLocal<String> threadLocal =
                  ThreadLocal.withInitial(new Supplier<String>() {
                      @Override
                      public String get() {
                          System.out.println("執行 withInitial() 方法");
                          return "默認值";
                      }
                  });
          public static void main(String[] args) {
              // 線(xiàn)程執行任務(wù)
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      String threadName = Thread.currentThread().getName();
                      // 執行方法,打印線(xiàn)程中設置的值
                      print(threadName);
                  }
              };
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 1
              new Thread(runnable, "MyThread-1").start();
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 2
              new Thread(runnable, "MyThread-2").start();
          }
      
          /**
           * 打印線(xiàn)程中的 ThreadLocal 值
           * @param threadName 線(xiàn)程名稱(chēng)
           */
          private static void print(String threadName) {
              // 得到 ThreadLocal 中的值
              String result = threadLocal.get();
              // 打印結果
              System.out.println(threadName + " 得到值:" + result);
          }
      }

      通過(guò)上述的代碼發(fā)現,withInitial 方法的使用好和 initialValue 好像沒(méi)啥區別,那為啥還要造出兩個(gè)類(lèi)似的方法呢?客官莫著(zhù)急,繼續往下看。

      ③ 更簡(jiǎn)潔的 withInitial 使用
      withInitial 方法的優(yōu)勢在于可以更簡(jiǎn)單的實(shí)現變量初始化,如下代碼所示:

      public class ThreadLocalByInitExample {
          // 定義 ThreadLocal
          private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "默認值");
          public static void main(String[] args) {
              // 線(xiàn)程執行任務(wù)
              Runnable runnable = new Runnable() {
                  @Override
                  public void run() {
                      String threadName = Thread.currentThread().getName();
                      // 執行方法,打印線(xiàn)程中設置的值
                      print(threadName);
                  }
              };
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 1
              new Thread(runnable, "MyThread-1").start();
              // 創(chuàng  )建并啟動(dòng)線(xiàn)程 2
              new Thread(runnable, "MyThread-2").start();
          }
      
          /**
           * 打印線(xiàn)程中的 ThreadLocal 值
           * @param threadName 線(xiàn)程名稱(chēng)
           */
          private static void print(String threadName) {
              // 得到 ThreadLocal 中的值
              String result = threadLocal.get();
              // 打印結果
              System.out.println(threadName + " 得到值:" + result);
          }
      }

      以上程序的執行結果為:

      4.ThreadLocal 版時(shí)間格式化

      了解了 ThreadLocal 的使用之后,我們回到本文的主題,接下來(lái)我們將使用 ThreadLocal 來(lái)實(shí)現 1000 個(gè)時(shí)間的格式化,具體實(shí)現代碼如下:

      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.concurrent.LinkedBlockingQueue;
      import java.util.concurrent.ThreadPoolExecutor;
      import java.util.concurrent.TimeUnit;
      
      public class MyThreadLocalByDateFormat {
          // 創(chuàng  )建 ThreadLocal 并設置默認值
          private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
                  ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));
      
          public static void main(String[] args) {
              // 創(chuàng  )建線(xiàn)程池執行任務(wù)
              ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,
                      TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
              // 執行任務(wù)
              for (int i = 0; i < 1000; i++) {
                  int finalI = i;
                  // 執行任務(wù)
                  threadPool.execute(new Runnable() {
                      @Override
                      public void run() {
                          // 得到時(shí)間對象
                          Date date = new Date(finalI * 1000);
                          // 執行時(shí)間格式化
                          formatAndPrint(date);
                      }
                  });
              }
              // 線(xiàn)程池執行完任務(wù)之后關(guān)閉
              threadPool.shutdown();
              // 線(xiàn)程池執行完任務(wù)之后關(guān)閉
              threadPool.shutdown();
          }
          /**
           * 格式化并打印時(shí)間
           * @param date 時(shí)間對象
           */
          private static void formatAndPrint(Date date) {
              // 執行格式化
              String result = dateFormatThreadLocal.get().format(date);
              // 打印最終結果
              System.out.println("時(shí)間:" + result);
          }
      }

      以上程序的執行結果為:

      從上述結果可以看出,使用 ThreadLocal 也可以解決線(xiàn)程并發(fā)問(wèn)題,并且避免了代碼加鎖排隊執行的問(wèn)題。

      使用場(chǎng)景2:跨類(lèi)傳遞數據

      除了上面的使用場(chǎng)景之外,我們還可以使用 **ThreadLocal** 來(lái)實(shí)現線(xiàn)程中跨類(lèi)、跨方法的數據傳遞。比如登錄用戶(hù)的 User 對象信息,我們需要在不同的子系統中多次使用,如果使用傳統的方式,我們需要使用方法傳參和返回值的方式來(lái)傳遞 User 對象,然而這樣就無(wú)形中造成了類(lèi)和類(lèi)之間,甚至是系統和系統之間的相互耦合了,所以此時(shí)我們可以使用 ThreadLocal 來(lái)實(shí)現 User 對象的傳遞。

      確定了方案之后,接下來(lái)我們來(lái)實(shí)現具體的業(yè)務(wù)代碼。我們可以先在主線(xiàn)程中構造并初始化一個(gè) User 對象,并將此 User 對象存儲在 ThreadLocal 中,存儲完成之后,我們就可以在同一個(gè)線(xiàn)程的其他類(lèi)中,如倉儲類(lèi)或訂單類(lèi)中直接獲取并使用 User 對象了,具體實(shí)現代碼如下。

      主線(xiàn)程中的業(yè)務(wù)代碼:

      public class ThreadLocalByUser {
          public static void main(String[] args) {
              // 初始化用戶(hù)信息
              User user = new User("Java");
              // 將 User 對象存儲在 ThreadLocal 中
              UserStorage.setUser(user);
              // 調用訂單系統
              OrderSystem orderSystem = new OrderSystem();
              // 添加訂單(方法內獲取用戶(hù)信息)
              orderSystem.add();
              // 調用倉儲系統
              RepertorySystem repertory = new RepertorySystem();
              // 減庫存(方法內獲取用戶(hù)信息)
              repertory.decrement();
          }
      }

      User 實(shí)體類(lèi):

      /**
       * 用戶(hù)實(shí)體類(lèi)
       */
      class User {
          public User(String name) {
              this.name = name;
          }
          private String name;
          public String getName() {
              return name;
          }
          public void setName(String name) {
              this.name = name;
          }
      }

      ThreadLocal 操作類(lèi):

      /**
       * 用戶(hù)信息存儲類(lèi)
       */
      class UserStorage {
          // 用戶(hù)信息
          public static ThreadLocal<User> USER = new ThreadLocal();
      
          /**
           * 存儲用戶(hù)信息
           * @param user 用戶(hù)數據
           */
          public static void setUser(User user) {
              USER.set(user);
          }
      }

      訂單類(lèi):

      /**
       * 訂單類(lèi)
       */
      class OrderSystem {
          /**
           * 訂單添加方法
           */
          public void add() {
              // 得到用戶(hù)信息
              User user = UserStorage.USER.get();
              // 業(yè)務(wù)處理代碼(忽略)...
              System.out.println(String.format("訂單系統收到用戶(hù):%s 的請求。",
                      user.getName()));
          }
      }

      倉儲類(lèi):

      /**
       * 倉儲類(lèi)
       */
      class RepertorySystem {
          /**
           * 減庫存方法
           */
          public void decrement() {
              // 得到用戶(hù)信息
              User user = UserStorage.USER.get();
              // 業(yè)務(wù)處理代碼(忽略)...
              System.out.println(String.format("倉儲系統收到用戶(hù):%s 的請求。",
                      user.getName()));
          }
      }

      以上程序的最終執行結果:

      從上述結果可以看出,當我們在主線(xiàn)程中先初始化了 User 對象之后,訂單類(lèi)和倉儲類(lèi)無(wú)需進(jìn)行任何的參數傳遞也可以正常獲得 User 對象了,從而實(shí)現了一個(gè)線(xiàn)程中,跨類(lèi)和跨方法的數據傳遞。

      總結

      使用 ThreadLocal 可以創(chuàng )建線(xiàn)程私有變量,所以不會(huì )導致線(xiàn)程安全問(wèn)題,同時(shí)使用 ThreadLocal 還可以避免因為引入鎖而造成線(xiàn)程排隊執行所帶來(lái)的性能消耗;再者使用 ThreadLocal 還可以實(shí)現一個(gè)線(xiàn)程內跨類(lèi)、跨方法的數據傳遞。

      以上就是Java ThreadLocal的使用場(chǎng)景總結的詳細內容,更多關(guān)于Java ThreadLocal的使用場(chǎng)景的資料請關(guān)注腳本之家其它相關(guān)文章!

      免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。

      黑人又粗又大BBBXXX| 亚洲中文字幕无码天然素人在线| 国内精品久久久久伊人AV| 精品久久久久久亚洲精品| 人人做人人妻人人精| 欧美国产日韩A在线观看|