最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SimpleDateFormat在多線程下的安全問題及解決

 更新時間:2026年03月07日 14:58:59   作者:zmbwcx2003  
SimpleDateFormat類在Java中用于日期時間的格式化和解析,但在高并發(fā)環(huán)境下存在線程不安全的問題,原因是它共享了一個Calendar對象,導致多個線程操作時出現(xiàn)沖突,為了解決這個問題,可以使用局部變量、加鎖或使用線程安全的DateTimeFormatter類

情景重現(xiàn)

SimpleDateFormat類是Java開發(fā)中的一個日期時間的轉化類。它可以滿足絕大多數(shù)的開發(fā)場景,但是在高并發(fā)下會出現(xiàn)并發(fā)問題。

接下來查看下文中的案例。

public class TestSimpleDateFormat {
    public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    Date parse = format.parse("2003-01-01");
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

上面代碼簡單來說就是創(chuàng)建了一個SimpleDateFormat類對象,該對象被后續(xù)會被五個線程使用,去轉化日期格式并打印。

我們來查看輸出結果:

java.lang.NumberFormatException: empty String
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: multiple points
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: empty String
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
    at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
    at java.lang.Double.parseDouble(Double.java:538)
    at java.text.DigitList.getDouble(DigitList.java:169)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at test.lambda$main$0(test.java:21)
    at java.lang.Thread.run(Thread.java:745)
Fri Nov 01 00:00:00 CST 2222

可以看到,只輸出了一次時間轉化,并且該輸出格式還是錯誤的。

接下來我們來查看為什么SimpleDateFormat類是線程不安全的。

SimpleDateFormat解析

我們根據(jù)parse()方法,查看SimpleDateFormat是如何進行格式轉換的。

我們可以看到,返回結果是根據(jù)另一個方法獲取到的,接下來我們接著查看該parse()源碼。

這是一個抽象方法,接著我們?nèi)ゲ榭此木唧w實現(xiàn)。

可以看到該方法很長,但是我們只關注返回如何結果,直接拉到最后查看該方法如何返回一個日期格式 。上圖中,最后一次修改parseDate對象是在箭頭的位置。那么我們查看getTime()方法。

可以看到該方法是由Calendar類提供的,該類名翻譯為中文就是日歷的意思,并且返回結果也是我們需要的日期格式,那么我們就可以確定該方法用于給parseDate對象提供返回值的,接下來回退一下查看其他方法哪個是提供Calendar對象來調用getTime()方法的。

現(xiàn)在我們清楚了Calendar類對象是由establish()方法提供的了,該方法中需要一個參數(shù)calendar對象。該對象由SimpleDateFormat的父類DateFormat來維護。

此時我們或許大概明白是因為SimpleDateFormat類之所以線程不安全的問題是因為在多線程下共享了calendar對象。接下來我們繼續(xù)查看establish()方法,驗證是否是這樣,下面是具體源碼

    Calendar establish(Calendar cal) {
        boolean weekDate = isSet(WEEK_YEAR)
                       && field[WEEK_YEAR] > field[YEAR];
        if (weekDate && !cal.isWeekDateSupported()) {
            // Use YEAR instead
            if (!isSet(YEAR)) {
                set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
            }
            weekDate = false;
        }

        cal.clear();
        // Set the fields from the min stamp to the max stamp so that
        // the field resolution works in the Calendar.
        for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
            for (int index = 0; index <= maxFieldIndex; index++) {
                if (field[index] == stamp) {
                    cal.set(index, field[MAX_FIELD + index]);
                    break;
                }
            }
        }

        if (weekDate) {
            int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
            int dayOfWeek = isSet(DAY_OF_WEEK) ?
                                field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
            if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
                if (dayOfWeek >= 8) {
                    dayOfWeek--;
                    weekOfYear += dayOfWeek / 7;
                    dayOfWeek = (dayOfWeek % 7) + 1;
                } else {
                    while (dayOfWeek <= 0) {
                        dayOfWeek += 7;
                        weekOfYear--;
                    }
                }
                dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
            }
            cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
        }
        return cal;
    }

可以看到,在該方法中,對cal對象執(zhí)行了clear()方法與set()方法,我們查看clear方法是做什么的

該方法提供了類似初始化的功能,將上一次的格式轉化保存的cal屬性清除。

而set()方法將本次的格式轉換需要的數(shù)據(jù)更新。因此,我們可以確定了SimpleDateFormat類之所以線程不安全就是因為共享了calendar對象。

解決方案

為了避免SimpleDateFormat格式轉換帶來的并發(fā)問題,我們可以采取以下幾個措施

局部變量

我們已經(jīng)知道了產(chǎn)生線程安全問題的原因是共享了相同屬性,那么我們只要讓每個線程都包含自己的屬性就可以避免該問題的發(fā)生。

具體實現(xiàn)代碼如下:

public class TestSimpleDateFormat {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
        new Thread(()->{
            try {
                    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
                    Date parse = format.parse("2003-01-01");
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

運行結果如下:

Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003
Wed Jan 01 00:00:00 CST 2003

這種方式不太推薦,因為會創(chuàng)建大量的SimpleDateFormat對象,占用內(nèi)存空間。 

加鎖

除了讓每個線程都擁有自己獨立的對象外,我們也可以保證在同一時刻下,只有一個線程對共享屬性進行修改,那就是加鎖。具體實現(xiàn)代碼如下

public class TestSimpleDateFormat{
    public static void main(String[] args) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Date parse;
                    synchronized (format){
                        parse = format.parse("2003-01-01");
                    }
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

但是這種方法不太推薦,因為能夠出現(xiàn)格式轉化錯誤的情況已經(jīng)是很大的并發(fā)了,如果還使用同步鎖的話會影響性能。

使用線程變量

public class test {
    private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    Date parse = threadLocal.get().parse("2023-01-01");
                    System.out.println(parse);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

使用DateTimeFormatter

在JDK8之后提供了線程安全的格式轉化DateTimeFormatter類,使用方法如下

public class test {
    public static void main(String[] args) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    TemporalAccessor parse = dateTimeFormatter.parse("2003-06-03");
                        System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

輸出結果為:

{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03
{},ISO resolved to 2003-06-03

總結

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • java中判斷字符串數(shù)組是否包含特定字符串的三種方法實現(xiàn)與對比

    java中判斷字符串數(shù)組是否包含特定字符串的三種方法實現(xiàn)與對比

    這篇文章主要為大家詳細介紹了java中判斷字符串數(shù)組是否包含特定字符串的三種方法實現(xiàn)與對比,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下
    2025-12-12
  • Java?導出Excel增加下拉框選項

    Java?導出Excel增加下拉框選項

    這篇文章主要介紹了Java?導出Excel增加下拉框選項,excel對于下拉框較多選項的,需要使用隱藏工作簿來解決,使用函數(shù)取值來做選項,下文具體的操作詳情,需要的小伙伴可以參考一下
    2022-04-04
  • spring boot使用thymeleaf為模板的基本步驟介紹

    spring boot使用thymeleaf為模板的基本步驟介紹

    Spring Boot項目的默認模板引擎是Thymeleaf,這沒什么好說的,個人覺得也非常好,下面這篇文章主要給大家介紹了關于spring boot使用thymeleaf為模板的相關資料,需要的朋友可以參考借鑒,下面來一起學習學習吧。
    2018-01-01
  • Java Socket聊天室編程(二)之利用socket實現(xiàn)單聊聊天室

    Java Socket聊天室編程(二)之利用socket實現(xiàn)單聊聊天室

    這篇文章主要介紹了Java Socket聊天室編程(二)之利用socket實現(xiàn)單聊聊天室的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-09-09
  • 使用Jmeter進行http接口測試的詳細流程

    使用Jmeter進行http接口測試的詳細流程

    本文主要針對http接口進行測試,使用Jmeter工具實現(xiàn),  Jmter工具設計之初是用于做性能測試的,它在實現(xiàn)對各種接口的調用方面已經(jīng)做的比較成熟,因此,本次直接使用Jmeter工具來完成對Http接口的測試,需要的朋友可以參考下
    2024-12-12
  • SpringBoot中自動配置原理解析

    SpringBoot中自動配置原理解析

    SpringBoost是基于Spring框架開發(fā)出來的功能更強大的Java程序開發(fā)框架,本文將以廣角視覺來剖析SpringBoot自動配置的原理,涉及部分Spring、SpringBoot源碼,需要的可以參考下
    2023-11-11
  • java中ThreadLocal和ThreadLocalMap淺析

    java中ThreadLocal和ThreadLocalMap淺析

    這篇文章主要介紹了java中ThreadLocal和ThreadLocalMap淺析,ThreadLocal類用來設置線程私有變量?本身不儲存值?主要提供自身引用?和?操作ThreadLocalMap?屬性值得方法,需要的朋友可以參考下
    2023-09-09
  • Springboot轉發(fā)重定向實現(xiàn)方式解析

    Springboot轉發(fā)重定向實現(xiàn)方式解析

    這篇文章主要介紹了springboot轉發(fā)重定向實現(xiàn)方式解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-03-03
  • Java經(jīng)典面試題匯總:網(wǎng)絡編程

    Java經(jīng)典面試題匯總:網(wǎng)絡編程

    本篇總結的是Java 網(wǎng)絡編程相關的面試題,后續(xù)會持續(xù)更新,希望我的分享可以幫助到正在備戰(zhàn)面試的實習生或者已經(jīng)工作的同行,如果發(fā)現(xiàn)錯誤還望大家多多包涵,不吝賜教,謝謝
    2021-07-07
  • 解讀java.lang.Character.isLetterOrDigit()的使用方式

    解讀java.lang.Character.isLetterOrDigit()的使用方式

    這篇文章主要介紹了解讀java.lang.Character.isLetterOrDigit()的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-06-06

最新評論

潢川县| 阿合奇县| 广宗县| 罗山县| 菏泽市| 搜索| 南宁市| 弥渡县| 恩平市| 惠州市| 曲周县| 绥中县| 曲水县| 安岳县| 天等县| 长岭县| 瑞丽市| 永泰县| 清苑县| 盐城市| 呼伦贝尔市| 宁远县| 塔河县| 石景山区| 南江县| 会东县| 五河县| 南城县| 伽师县| 汕尾市| 庐江县| 滦平县| 汶上县| 象州县| 景洪市| 敦煌市| 黄龙县| 胶南市| 柏乡县| 淳安县| 轮台县|