亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析Java中SimpleDateFormat為什么是線程不安全的

 更新時間:2024年02月20日 09:22:55   作者:哪吒編程  
SimpleDateFormat是Java中用于日期時間格式化的一個類,它提供了對日期的解析和格式化能力,本文主要來和大家一起探討一下SimpleDateFormat為什么是線程不安全的,感興趣的可以了解下

在日常開發(fā)中,Date工具類使用頻率相對較高,大家通常都會這樣寫:

public static Date getData(String date) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.parse(date);
}

public static Date getDataByFormat(String date, String format) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    return sdf.parse(date);
}

這很簡單啊,有什么爭議嗎

你應(yīng)該聽過“時區(qū)”這個名詞,大家也都知道,相同時刻不同時區(qū)的時間是不一樣的。

因此在使用時間時,一定要給出時區(qū)信息。

public static void getDataByZone(String param, String format) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat(format);

    // 默認時區(qū)解析時間表示
    Date date = sdf.parse(param);
    System.out.println(date + ":" + date.getTime());

    // 東京時區(qū)解析時間表示
    sdf.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
    Date newYorkDate = sdf.parse(param);
    System.out.println(newYorkDate + ":" + newYorkDate.getTime());
}

public static void main(String[] args) throws ParseException {
   getDataByZone("2023-11-10 10:00:00","yyyy-MM-dd HH:mm:ss");
}

對于當前的上海時區(qū)和紐約時區(qū),轉(zhuǎn)化為 UTC 時間戳是不同的時間。

對于同一個本地時間的表示,不同時區(qū)的人解析得到的 UTC 時間一定是不同的,反過來不同的本地時間可能對應(yīng)同一個 UTC。

格式化后出現(xiàn)的時間錯亂。

public static void getDataByZoneFormat(String param, String format) throws ParseException {
   SimpleDateFormat sdf = new SimpleDateFormat(format);
    Date date = sdf.parse(param);
    // 默認時區(qū)格式化輸出
    System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));
    // 東京時區(qū)格式化輸出
    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
    System.out.println(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss Z]").format(date));
}

public static void main(String[] args) throws ParseException {
   getDataByZoneFormat("2023-11-10 10:00:00","yyyy-MM-dd HH:mm:ss");
}

我當前時區(qū)的 Offset(時差)是 +8 小時,對于 +9 小時的紐約,整整差了1個小時,北京早上 10 點對應(yīng)早上東京 11 點。

看看Java 8是如何解決時區(qū)問題的:

Java 8 推出了新的時間日期類 ZoneId、ZoneOffset、LocalDateTime、ZonedDateTime 和 DateTimeFormatter,處理時區(qū)問題更簡單清晰。

public static void getDataByZoneFormat8(String param, String format) throws ParseException {
    ZoneId zone = ZoneId.of("Asia/Shanghai");
    ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
    ZoneId timeZone = ZoneOffset.ofHours(2);

    // 格式化器
    DateTimeFormatter dtf = DateTimeFormatter.ofPattern(format);
    ZonedDateTime date = ZonedDateTime.of(LocalDateTime.parse(param, dtf), zone);

    // withZone設(shè)置時區(qū)
    DateTimeFormatter dtfz = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
    System.out.println(dtfz.withZone(zone).format(date));
    System.out.println(dtfz.withZone(tokyoZone).format(date));
    System.out.println(dtfz.withZone(timeZone).format(date));
}

public static void main(String[] args) throws ParseException {
    getDataByZoneFormat8("2023-11-10 10:00:00","yyyy-MM-dd HH:mm:ss");
}
  • Asia/Shanghai對應(yīng)+8,對應(yīng)2023-11-10 10:00:00;
  • Asia/Tokyo對應(yīng)+9,對應(yīng)2023-11-10 11:00:00;
  • timeZone 是+2,所以對應(yīng)2023-11-10 04:00:00;

在處理帶時區(qū)的國際化時間問題,推薦使用jdk8的日期時間類:

  • 通過ZoneId,定義時區(qū);
  • 使用ZonedDateTime保存時間;
  • 通過withZone對DateTimeFormatter設(shè)置時區(qū);
  • 進行時間格式化得到本地時間;

思路比較清晰,不容易出錯。

在與前端聯(lián)調(diào)時,報了個錯,java.lang.NumberFormatException: multiple points,起初我以為是時間格式傳的不對,仔細一看,不對啊。

百度一下,才知道是高并發(fā)情況下SimpleDateFormat有線程安全的問題。

下面通過模擬高并發(fā),把這個問題復(fù)現(xiàn)一下:

public static void getDataByThread(String param, String format) throws InterruptedException {
    ExecutorService threadPool = Executors.newFixedThreadPool(5);
    SimpleDateFormat sdf = new SimpleDateFormat(format);
    // 模擬并發(fā)環(huán)境,開啟5個并發(fā)線程
    for (int i = 0; i < 5; i++) {
        threadPool.execute(() -> {
            for (int j = 0; j < 2; j++) {
                try {
                    System.out.println(sdf.parse(param));
                } catch (ParseException e) {
                    System.out.println(e);
                }
            }
        });
    }
    threadPool.shutdown();

    threadPool.awaitTermination(1, TimeUnit.HOURS);
}

果不其然,報錯。還將2023年轉(zhuǎn)換成2220年,我勒個乖乖。

在時間工具類里,時間格式化,我都是這樣弄的啊,沒問題啊,為啥這個不行?原來是因為共用了同一個SimpleDateFormat,在工具類里,一個線程一個SimpleDateFormat,當然沒問題啦!

可以通過TreadLocal 局部變量,解決SimpleDateFormat的線程安全問題。

public static void getDataByThreadLocal(String time, String format) throws InterruptedException {
    ExecutorService threadPool = Executors.newFixedThreadPool(5);

    ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat(format);
        }
    };

    // 模擬并發(fā)環(huán)境,開啟5個并發(fā)線程
    for (int i = 0; i < 5; i++) {
        threadPool.execute(() -> {
            for (int j = 0; j < 2; j++) {
                try {
                    System.out.println(sdf.get().parse(time));
                } catch (ParseException e) {
                    System.out.println(e);
                }
            }
        });
    }
    threadPool.shutdown();

    threadPool.awaitTermination(1, TimeUnit.HOURS);
}

看一下SimpleDateFormat.parse的源碼:

public class SimpleDateFormat extends DateFormat {
	@Override
	public Date parse(String text, ParsePosition pos){
		CalendarBuilder calb = new CalendarBuilder();
		
		Date parsedDate;
		try {
		    parsedDate = calb.establish(calendar).getTime();
		    // If the year value is ambiguous,
		    // then the two-digit year == the default start year
		    if (ambiguousYear[0]) {
		        if (parsedDate.before(defaultCenturyStart)) {
		            parsedDate = calb.addYear(100).establish(calendar).getTime();
		        }
		    }
		}
	}
}

class CalendarBuilder {
	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;
	            }
	        }
	    }
		...
	
	}
}
  • 先new CalendarBuilder();
  • 通過parsedDate = calb.establish(calendar).getTime();解析時間;
  • establish方法內(nèi)先cal.clear(),再重新構(gòu)建cal,整個操作沒有加鎖;

上面幾步就會導(dǎo)致在高并發(fā)場景下,線程1正在操作一個Calendar,此時線程2又來了。線程1還沒來得及處理 Calendar 就被線程2清空了。

因此,通過編寫Date工具類,一個線程一個SimpleDateFormat,還是有一定道理的。

以上就是淺析Java中SimpleDateFormat為什么是線程不安全的的詳細內(nèi)容,更多關(guān)于Java SimpleDateFormat線程不安全的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java8中Optional操作的實際應(yīng)用

    Java8中Optional操作的實際應(yīng)用

    Optional類是一個可以為null的容器對象,如果值存在則isPresent()方法會返回true,調(diào)用get()方法會返回該對象,下面這篇文章主要給大家介紹了關(guān)于Java8中Optional操作實際應(yīng)用的相關(guān)資料,需要的朋友可以參考下
    2022-02-02
  • springmvc Rest風(fēng)格介紹及實現(xiàn)代碼示例

    springmvc Rest風(fēng)格介紹及實現(xiàn)代碼示例

    這篇文章主要介紹了springmvc Rest風(fēng)格介紹及實現(xiàn)代碼示例,rest風(fēng)格簡潔,分享了HiddenHttpMethodFilter 的源碼,通過Spring4.0實現(xiàn)rest風(fēng)格源碼及簡單錯誤分析,具有一定參考價值,需要的朋友可以了解下。
    2017-11-11
  • Arrays.asList方法總結(jié)

    Arrays.asList方法總結(jié)

    本文主要對Arrays.asList方法進行總結(jié)。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02
  • java設(shè)計模式之代理模式(Porxy)詳解

    java設(shè)計模式之代理模式(Porxy)詳解

    這篇文章主要為大家詳細介紹了java設(shè)計模式之代理模式Porxy的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • IntelliJ IDEA(2017)安裝和破解的方法

    IntelliJ IDEA(2017)安裝和破解的方法

    這篇文章主要介紹了IntelliJ IDEA(2017)安裝和破解的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • 半小時通透Java的泛型

    半小時通透Java的泛型

    這篇文章主要給大家介紹了關(guān)于Java中泛型使用的方法,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-09-09
  • Spring?Boot與Spring?MVC?Spring對比及核心概念

    Spring?Boot與Spring?MVC?Spring對比及核心概念

    這篇文章主要為大家介紹了Spring?Boot與Spring?MVC?Spring的對比以及你需要了解的核心概念,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • java多線程從入門到精通看這篇就夠了

    java多線程從入門到精通看這篇就夠了

    熟悉 Java 多線程編程的同學(xué)都知道,當我們線程創(chuàng)建過多時,容易引發(fā)內(nèi)存溢出,因此我們就有必要使用線程池的技術(shù)了,今天通過本文給大家分享java多線程從入門到精通的相關(guān)知識,一起看看吧
    2021-06-06
  • SpringCloud Feign客戶端使用流程

    SpringCloud Feign客戶端使用流程

    在springcloud中,openfeign是取代了feign作為負載均衡組件的,feign最早是netflix提供的,他是一個輕量級的支持RESTful的http服務(wù)調(diào)用框架,內(nèi)置了ribbon,而ribbon可以提供負載均衡機制,因此feign可以作為一個負載均衡的遠程服務(wù)調(diào)用框架使用
    2023-01-01
  • Java如何利用策略模式替代if/else語句

    Java如何利用策略模式替代if/else語句

    這篇文章主要介紹了Java如何利用策略模式替代if/else語句,幫助大家優(yōu)化自己的代碼,提高程序運行效率,感興趣的朋友可以了解下
    2020-09-09

最新評論