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

          還在用 SimpleDateFormat 做時間格式化?小心背鍋!

          共 19120字,需瀏覽 39分鐘

           ·

          2022-08-08 16:27

          超全技術棧的Java入門+進階+實戰(zhàn)!(非白嫖,點擊查看)

          后端開發(fā)經常通過SimpleDateFormat對時間進行格式化,但是在多線程環(huán)境下,SimpleDateFormat存在線程安全問題,今天就一起來分析一下問題點,并看看如何解決,防止一不小心踩坑。

          一、SimpleDateFormat .parse() 方法的線程安全問題

          1.1 錯誤示例

          錯誤使用SimpleDateFormat .parse()的代碼如下:

          import java.text.SimpleDateFormat;

          public class SimpleDateFormatTest {
              private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

              public static void main(String[] args) {

                  
                  for (int i = 0; i < 20; ++i) {
                      Thread thread = new Thread(() -> {
                          try {
                              
                              System.out.println(Thread.currentThread().getName() + "--" + SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00"));
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }, "Thread-" + i);
                      thread.start();
                  }
              }
          }

          報錯:

          1.2 非線程安全原因分析

          查看源碼中可以看到:SimpleDateFormat繼承DateFormat類,SimpleDateFormat轉換日期是通過繼承自DateFormat類的Calendar對象來操作的,Calendar對象會被用來進行日期-時間計算,既被用于format方法也被用于parse方法。SimpleDateFormat 的 parse(String source) 方法 會調用繼承自父類的 DateFormat 的 parse(String source) 方法

          DateFormat 的 parse(String source) 方法會調用SimpleDateFormat中重寫的 parse(String text, ParsePosition pos) 方法,該方法中有個地方需要關注SimpleDateFormat 中重寫的 parse(String text, ParsePosition pos) 方法中調用了 establish(calendar) 這個方法:該方法中調用了 Calendar 的 clear() 方法

          可以發(fā)現整個過程中Calendar對象它并不是線程安全的,如果,a線程將calendar清空了,calendar 就沒有新值了,恰好此時b線程剛好進入到parse方法用到了calendar對象,那就會產生線程安全問題了!

          正常情況下:

          非線程安全的流程:

          1.3 解決方法

          方法1:每個線程都new一個SimpleDateFormat
          import java.text.SimpleDateFormat;

          public class SimpleDateFormatTest {

              public static void main(String[] args) {
                  for (int i = 0; i < 20; ++i) {
                      Thread thread = new Thread(() -> {
                          try {
                              
                              SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                              System.out.println(Thread.currentThread().getName() + "--" + simpleDateFormat.parse("2020-06-01 11:35:00"));
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }, "Thread-" + i);
                      thread.start();
                  }
              }
          }
          方式2:synchronized等方式加鎖
          public class SimpleDateFormatTest {
              private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

              public static void main(String[] args) {

                  for (int i = 0; i < 20; ++i) {
                      Thread thread = new Thread(() -> {
                          try {
                              synchronized (SIMPLE_DATE_FORMAT) {
                                  System.out.println(Thread.currentThread().getName() + "--" + SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00"));
                              }
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }, "Thread-" + i);
                      thread.start();
                  }
              }
          }
          方式3:使用ThreadLocal 為每個線程創(chuàng)建一個獨立變量
          import java.text.DateFormat;
          import java.text.SimpleDateFormat;

          public class SimpleDateFormatTest {

              private static final ThreadLocal<DateFormat> SAFE_SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

              public static void main(String[] args) {

                  for (int i = 0; i < 20; ++i) {
                      Thread thread = new Thread(() -> {
                          try {
                                  System.out.println(Thread.currentThread().getName() + "--" + SAFE_SIMPLE_DATE_FORMAT.get().parse("2020-06-01 11:35:00"));
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }, "Thread-" + i);
                      thread.start();
                  }
              }
          }

          二、SimpleDateFormat .format() 方法的線程安全問題

          2.1 錯誤示例

          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 SimpleDateFormatTest {
              
              private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

              public static void main(String[] args) throws InterruptedException {
                  
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                          101060, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));

                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      
                      threadPool.execute(new Runnable() {
                          @Override
                          public void run() {
                              Date date = new Date(finalI * 1000); 
                              formatAndPrint(date); 
                          }
                      });
                  }
                  threadPool.shutdown(); 
              }

              
              private static void formatAndPrint(Date date) {
                  String result = simpleDateFormat.format(date); 
                  System.out.println("時間:" + result); 
              }
          }

          從上述結果可以看出,程序的打印結果竟然有重復內容的,正確的情況應該是沒有重復的時間才對。

          2.2 非線程安全原因分析

          為了找到問題所在,查看 SimpleDateFormat 中 format 方法的源碼來排查一下問題,format 源碼如下:

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

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

          正常的情況下,程序的執(zhí)行是這樣的:

          非線程安全的執(zhí)行流程是這樣的:

          2.3 解決方法

          同樣有三種解決方法

          方法1:每個線程都new一個SimpleDateFormat
          public class SimpleDateFormatTest {
             
              public static void main(String[] args) throws InterruptedException {
                  
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                          101060, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));

                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      
                      threadPool.execute(new Runnable() {
                          @Override
                          public void run() {
                              
                              Date date = new Date(finalI * 1000);
                              
                              formatAndPrint(date);
                          }
                      });
                  }
                  
                  threadPool.shutdown();
              }

              
              private static void formatAndPrint(Date date) {
                  String result = new SimpleDateFormat("mm:ss").format(date); 
                  System.out.println("時間:" + result); 
              }
          }
          方式2:synchronized等方式加鎖

          所有的線程必須排隊執(zhí)行某些業(yè)務才行,這樣無形中就降低了程序的運行效率了

          public class SimpleDateFormatTest {
              
              private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

              public static void main(String[] args) throws InterruptedException {
                  
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                          101060, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));

                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      
                      threadPool.execute(new Runnable() {
                          @Override
                          public void run() {
                              Date date = new Date(finalI * 1000); 
                              formatAndPrint(date); 
                          }
                      });
                  }
                  
                  threadPool.shutdown();
              }

              
              private static void formatAndPrint(Date date) {
                  
                  String result = null;
                  
                  synchronized (SimpleDateFormatTest.class{
                      result = simpleDateFormat.format(date);
                  }
                  
                  System.out.println("時間:" + result);
              }
          }
          方式3:使用ThreadLocal 為每個線程創(chuàng)建一個獨立變量
          public class SimpleDateFormatTest {
              
              private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
                      ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));

              public static void main(String[] args) {
                  
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(101060,
                          TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
                  
                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      
                      threadPool.execute(() -> {
                          Date date = new Date(finalI * 1000); 
                          formatAndPrint(date); 
                      });
                  }
                  threadPool.shutdown(); 
              }

              
              private static void formatAndPrint(Date date) {
                  String result = dateFormatThreadLocal.get().format(date); 
                  System.out.println("時間:" + result);  
              }
          }

          來源:blog.csdn.net/QiuHaoqian/article/details/116594422


          胖虎聯合一線大廠朋友花費8個月的時間,錄制了一份Java入門+進階視頻教程

          課程特色:

          1. 總共88G,時常高達365小時,覆蓋所有主流技術棧

          2. 均為同一人錄制,不是東拼西湊的

          3. 對標線下T0級別的培訓課,講師大廠架構師,多年授課經驗,通俗易懂

          4. 內容豐富,每一個技術點除了視頻,還有課堂源碼、筆記、PPT、圖解

          5. 五大實戰(zhàn)項目(視頻+源碼+筆記+SQL+軟件)

          6. 一次付費,持續(xù)更新,永無二次費用

          點擊下方超鏈接查看詳情(或者點擊文末閱讀原文):

          (點擊查看)  88G,超全技術棧的Java入門+進階+實戰(zhàn)!

          瀏覽 45
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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 |