<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 做時(shí)間格式化?小心項(xiàng)目崩掉!

          共 20874字,需瀏覽 42分鐘

           ·

          2022-07-06 11:15

          關(guān)注我們,設(shè)為星標(biāo),每天7:40不見不散,架構(gòu)路上與您共享

          回復(fù)架構(gòu)師獲取資源


          大家好,我是你們的朋友架構(gòu)君,一個(gè)會(huì)寫代碼吟詩的架構(gòu)師。

          'javajgs.com';


          • SimpleDateFormat.parse() 方法的線程安全問題
            • 錯(cuò)誤示例
            • 非線程安全原因分析
            • 解決方法
          • SimpleDateFormat.format() 方法的線程安全問題
            • 錯(cuò)誤示例
            • 非線程安全原因分析
            • 解決方法

          SimpleDateFormat在多線程環(huán)境下存在線程安全問題。

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

          1.1 錯(cuò)誤示例

          錯(cuò)誤使用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) {

                  /**
                   * SimpleDateFormat線程不安全,沒有保證線程安全(沒有加鎖)的情況下,禁止使用全局SimpleDateFormat,否則報(bào)錯(cuò) NumberFormatException
                   *
                   * private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                   */

                  for (int i = 0; i < 20; ++i) {
                      Thread thread = new Thread(() -> {
                          try {
                              // 錯(cuò)誤寫法會(huì)導(dǎo)致線程安全問題
                              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();
                  }
              }
          }

          報(bào)錯(cuò):

          1.2 非線程安全原因分析

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

          SimpleDateFormat 的 parse(String source) 方法 會(huì)調(diào)用繼承自父類的 DateFormat 的 parse(String source) 方法

          DateFormat 的 parse(String source) 方法會(huì)調(diào)用SimpleDateFormat中重寫的 parse(String text, ParsePosition pos) 方法,該方法中有個(gè)地方需要關(guān)注

          SimpleDateFormat 中重寫的 parse(String text, ParsePosition pos) 方法中調(diào)用了 establish(calendar) 這個(gè)方法:

          該方法中調(diào)用了 Calendar 的 clear() 方法

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

          正常情況下:

          非線程安全的流程:

          1.3 解決方法

          方法1:每個(gè)線程都new一個(gè)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 {
                              // 每個(gè)線程都new一個(gè)
                              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 為每個(gè)線程創(chuàng)建一個(gè)獨(dú)立變量

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

          ThreadLocal的詳細(xì)使用細(xì)節(jié)見:

          https://blog.csdn.net/QiuHaoqian/article/details/117077792

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

          2.1 錯(cuò)誤示例
          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 {
              // 時(shí)間格式化對象
              private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

              public static void main(String[] args) throws InterruptedException {
                  // 創(chuàng)建線程池執(zhí)行任務(wù)
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                          101060, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));

                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      // 執(zhí)行任務(wù)
                      threadPool.execute(new Runnable() {
                          @Override
                          public void run() {
                              Date date = new Date(finalI * 1000); // 得到時(shí)間對象
                              formatAndPrint(date); // 執(zhí)行時(shí)間格式化
                          }
                      });
                  }
                  threadPool.shutdown(); // 線程池執(zhí)行完任務(wù)之后關(guān)閉
              }

              /**
               * 格式化并打印時(shí)間
               */

              private static void formatAndPrint(Date date) {
                  String result = simpleDateFormat.format(date); // 執(zhí)行格式化
                  System.out.println("時(shí)間:" + result); // 打印最終結(jié)果
              }
          }

          從上述結(jié)果可以看出,程序的打印結(jié)果竟然有重復(fù)內(nèi)容的,正確的情況應(yīng)該是沒有重復(fù)的時(shí)間才對。

          2.2 非線程安全原因分析

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

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

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

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

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


          2.3 解決方法

          同樣有三種解決方法

          方法1:每個(gè)線程都new一個(gè)SimpleDateFormat

          public class SimpleDateFormatTest {
             
              public static void main(String[] args) throws InterruptedException {
                  // 創(chuàng)建線程池執(zhí)行任務(wù)
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                          101060, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));

                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      // 執(zhí)行任務(wù)
                      threadPool.execute(new Runnable() {
                          @Override
                          public void run() {
                              // 得到時(shí)間對象
                              Date date = new Date(finalI * 1000);
                              // 執(zhí)行時(shí)間格式化
                              formatAndPrint(date);
                          }
                      });
                  }
                  // 線程池執(zhí)行完任務(wù)之后關(guān)閉
                  threadPool.shutdown();
              }

              /**
               * 格式化并打印時(shí)間
               */

              private static void formatAndPrint(Date date) {
                  String result = new SimpleDateFormat("mm:ss").format(date); // 執(zhí)行格式化
                  System.out.println("時(shí)間:" + result); // 打印最終結(jié)果
              }
          }

          方式2:synchronized等方式加鎖

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

          public class SimpleDateFormatTest {
              // 時(shí)間格式化對象
              private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

              public static void main(String[] args) throws InterruptedException {
                  // 創(chuàng)建線程池執(zhí)行任務(wù)
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                          101060, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));

                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      // 執(zhí)行任務(wù)
                      threadPool.execute(new Runnable() {
                          @Override
                          public void run() {
                              Date date = new Date(finalI * 1000); // 得到時(shí)間對象
                              formatAndPrint(date); // 執(zhí)行時(shí)間格式化
                          }
                      });
                  }
                  // 線程池執(zhí)行完任務(wù)之后關(guān)閉
                  threadPool.shutdown();
              }

              /**
               * 格式化并打印時(shí)間
               */

              private static void formatAndPrint(Date date) {
                  // 執(zhí)行格式化
                  String result = null;
                  // 加鎖
                  synchronized (SimpleDateFormatTest.class{
                      result = simpleDateFormat.format(date);
                  }
                  // 打印最終結(jié)果
                  System.out.println("時(shí)間:" + result);
              }
          }

          方式3:使用ThreadLocal 為每個(gè)線程創(chuàng)建一個(gè)獨(dú)立變量

          public class SimpleDateFormatTest {
              // 創(chuàng)建 ThreadLocal 并設(shè)置默認(rèn)值
              private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
                      ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));

              public static void main(String[] args) {
                  // 創(chuàng)建線程池執(zhí)行任務(wù)
                  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(101060,
                          TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));
                  // 執(zhí)行任務(wù)
                  for (int i = 0; i < 1000; i++) {
                      int finalI = i;
                      // 執(zhí)行任務(wù)
                      threadPool.execute(() -> {
                          Date date = new Date(finalI * 1000); // 得到時(shí)間對象
                          formatAndPrint(date); // 執(zhí)行時(shí)間格式化
                      });
                  }
                  threadPool.shutdown(); // 線程池執(zhí)行完任務(wù)之后關(guān)閉
              }

              /**
               * 格式化并打印時(shí)間
               */

              private static void formatAndPrint(Date date) {
                  String result = dateFormatThreadLocal.get().format(date); // 執(zhí)行格式化
                  System.out.println("時(shí)間:" + result);  // 打印最終結(jié)果
              }
          }

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

          details/116594422


          到此文章就結(jié)束了。Java架構(gòu)師必看一個(gè)集公眾號、小程序、網(wǎng)站(3合1的文章平臺,給您架構(gòu)路上一臂之力,javajgs.com)。如果今天的文章對你在進(jìn)階架構(gòu)師的路上有新的啟發(fā)和進(jìn)步,歡迎轉(zhuǎn)發(fā)給更多人。歡迎加入架構(gòu)師社區(qū)技術(shù)交流群,眾多大咖帶你進(jìn)階架構(gòu)師,在后臺回復(fù)“加群”即可入群。



          這些年小編給你分享過的干貨


          1.idea永久激活碼(親測可用)

          2.優(yōu)質(zhì)ERP系統(tǒng)帶進(jìn)銷存財(cái)務(wù)生產(chǎn)功能(附源碼)

          3.優(yōu)質(zhì)SpringBoot帶工作流管理項(xiàng)目(附源碼)

          4.最好用的OA系統(tǒng),拿來即用(附源碼)

          5.SBoot+Vue外賣系統(tǒng)前后端都有(附源碼

          6.SBoot+Vue可視化大屏拖拽項(xiàng)目(附源碼)


          轉(zhuǎn)發(fā)在看就是最大的支持??

          瀏覽 46
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  97人人爽人人爽人人爽人人爽 | 亚洲第十页 | 直接看黄色电影 | 成人做爰A片免费播放金桔视频 | 人人操人人操人人操人人操 |