在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    還在用 SimpleDateFormat 做時(shí)間格式化?小心項(xiàng)目崩掉

    還在用 SimpleDateFormat 做時(shí)間格式化?小心項(xiàng)目崩掉


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

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

    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線程不安全,沒(méi)有保證線程安全(沒(méi)有加鎖)的情況下,禁止使用全局SimpleDateFormat,否則報(bào)錯(cuò) NumberFormatException * * private static final SimpleDateFormat dateFormat = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”); */ for (int i = 0; i { try { // 錯(cuò)誤寫法會(huì)導(dǎo)致線程安全問(wèn)題 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)換日期是通過(guò)繼承自DateFormat類的Calendar對(duì)象來(lái)操作的,Calendar對(duì)象會(huì)被用來(lái)進(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è)過(guò)程中Calendar對(duì)象它并不是線程安全的,如果,a線程將calendar清空了,calendar 就沒(méi)有新值了,恰好此時(shí)b線程剛好進(jìn)入到parse方法用到了calendar對(duì)象,那就會(huì)產(chǎn)生線程安全問(wè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 { 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 { 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 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 { 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é)見(jiàn):

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

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

    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í)間格式化對(duì)象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“mm:ss”); public static void main(String[] args) throws InterruptedException { // 創(chuàng)建線程池執(zhí)行任務(wù) ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, 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í)間對(duì)象 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)該是沒(méi)有重復(fù)的時(shí)間才對(duì)。

    2.2 非線程安全原因分析

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

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

    • 線程 1 執(zhí)行了 calendar.setTime(date) 方法,將用戶輸入的時(shí)間轉(zhuǎn)換成了后面格式化時(shí)所需要的時(shí)間;
    • 線程 1 暫停執(zhí)行,線程 2 得到 CPU 時(shí)間片開始執(zhí)行;
    • 線程 2 執(zhí)行了 calendar.setTime(date) 方法,對(duì)時(shí)間進(jìn)行了修改;
    • 線程 2 暫停執(zhí)行,線程 1 得出 CPU 時(shí)間的繼續(xù)執(zhí)行,因?yàn)榫€程 1 和線程 2 使用的是同一對(duì)象,而時(shí)間已經(jīng)被線程 2 修改了,所以此時(shí)當(dāng)前線程 1 繼續(xù)執(zhí)行的時(shí)候就會(huì)出現(xiàn)線程安全的問(wè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( 10, 10, 60, 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í)間對(duì)象 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ù)才行,這樣無(wú)形中就降低了程序的運(yùn)行效率了

    public class SimpleDateFormatTest { // 時(shí)間格式化對(duì)象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“mm:ss”); public static void main(String[] args) throws InterruptedException { // 創(chuàng)建線程池執(zhí)行任務(wù) ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, 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í)間對(duì)象 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 dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat(“mm:ss”)); public static void main(String[] args) { // 創(chuàng)建線程池執(zhí)行任務(wù) ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue(1000)); // 執(zhí)行任務(wù) for (int i = 0; i { Date date = new Date(finalI * 1000); // 得到時(shí)間對(duì)象 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é)果 }}

    文章來(lái)源:blog.csdn.net/QiuHaoqian/article/details/116594422

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場(chǎng),版權(quán)歸原作者所有,如有侵權(quán)請(qǐng)聯(lián)系管理員(admin#wlmqw.com)刪除。
    上一篇 2022年6月19日 18:09
    下一篇 2022年6月19日 18:09

    相關(guān)推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息