SimpleDateFormat线程安全使用方案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/caox_nazi/article/details/83302098

SimpleDateFormat线程安全使用方案

 1.问题背景:

SimpleDateFormat的隐患:

(1)结果值不对:转换的结果值经常会出人意料,和预期不同。

(2)内存泄漏: 由于转换的结果值不对,后续的一些操作,如一个循环,累加一天处理一个东西,但是生成的日期如果异常导致很大的话,会让这个循环变成一个类似死循环一样导致系统内存泄漏,频繁触发GC,造成系统不可用。

2.产生原因:

(1):因为SimpleDateFormat线程不安全,很多人都会写个Util类,然后把SimpleDateFormat定义成全局的一个常量,所有线程都共享这个常量:​​​​​,如下所示:

protected static final SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
public static Date formatDate(String date) throws ParseException {
 return dayFormat.parse(date);
}

(2)根本原因:SimpleDateFormat线程不安全,由于所有的格式化和解析都需要通过一个中间对象进行转换,那就是Calendar,而这个也是我们出现线程不安全的罪魁祸首,多个线程操作同一个Calendar的时候后来的线程会覆盖先来线程的数据,那最后其实返回的是后来线程的数据。

3.解决方案:

(1)新建SimpleDateFormat,每次使用的时候都创建一个新的SimpleDateFormat

public static Date formatDate(String date) throws ParseException {
 SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
 return dayFormat.parse(date);
}

【缺陷】:这个方法使用量比较大,有可能会频繁造成Young gc,整个系统还是会受一定的影响。

(2)使用ThreadLocal:

使用ThreadLocal能避免上面频繁的造成Young gc,我们对每个线程都使用ThreadLocal进行保存,由于ThreadLocal是线程之间隔离开的,所以不会出现线程安全问题:

package com.baofu.admin.ma.test;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by nazi on 2018/10/23.
 * @author  nazi
 */
@Slf4j
public class ThreadLocalDateTest {
    /**
     * 使用ThreadLocal将线程之间隔离开
     */
    private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();

    /**
     * 转换日期格式
     * @param date                 String型日期格式
     * @return                     Date型日期格式
     * @throws ParseException      抛出格式转换异常
     */
    private static Date formatDateStrToDate(String date) throws ParseException {
        SimpleDateFormat dayFormat = getSimpleDateFormat();
        return dayFormat.parse(date);
    }

    /**
     * 转换日期格式
     * @param date                  Date型日期格式
     * @return                     String型日期格式
     * @throws ParseException      抛出格式转换异常
     */
    private static String formatDateToString(Date date) throws ParseException{
        SimpleDateFormat dayFormat = getSimpleDateFormat();
        return dayFormat.format(date);
    }

    /**
     * 获取日期转换格式
     * @return            返回SimpleDateFormat
     */
    private static SimpleDateFormat getSimpleDateFormat() {
        SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
        if (simpleDateFormat == null){
            simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            simpleDateFormatThreadLocal.set(simpleDateFormat);
        }
        return simpleDateFormat;
    }

    @Test
    public  void test1() throws Exception{
        Date date = new Date();
        String dateStr = formatDateToString(date);
        log.info("call dateStr 字符型日期 :{}", dateStr);

        Date dateToDate = formatDateStrToDate(dateStr);
        log.info("call date Date型日期 :{}", dateToDate);
    }

}

(3)使用第三方工具包:joda-time(推荐)和 commons-lang:

DateTime dateTimePre = new DateTime();
log.info("call dateTimePre :{}",DateTime.now());
DateTime dateTime = new DateTime(2000, 1, 1, 1, 1, 1, 1);
System.out.println(dateTime.plusDays(90).toString("yyyy-MM-dd HH:mm:ssSSS"));

log.info("call common-lang Data :{}", FastDateFormat.getInstance().format(new Date()));

(4)最佳推荐:升级jdk8使用LocalDateTime:

  注意:此方案 在日期的格式化和解析方面不用考虑线程安全性

package com.baofu.admin.ma.test;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Date;

/**
 * Created by nazi on 2018/10/22.
 * @author  nazi
 * LocalDate无法包含时间;
 * LocalTime无法包含日期;
 * LocalDateTime才能同时包含日期和时间。
 */
@Slf4j
public class LocalDateTimeTest {

    /**
     * 将时间转换为固定类型的时间样式
     * @param time      当前时间
     * @param pattern   要转换的类型
     * @return          String型日期
     */
    private static String formatTime(LocalDateTime time, String pattern) {
        return time.format(DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 日期加上一个数,根据field不同加不同值,field为ChronoUnit.*
     * @param time      当前时间
     * @param number    要加的值
     * @param field     根据field不同加不同值
     * @return          LocalDateTime型日期
     */
    public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {
        return time.plus(number, field);
    }

    /**
     * 日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.*
     * @param time     当前时间
     * @param number   要减去的值
     * @param field    根据field不同加不同值
     * @return         LocalDateTime型日期
     */
    private static LocalDateTime minus(LocalDateTime time, long number, TemporalUnit field){
        return time.minus(number,field);
    }

    /**
     * Date转换为LocalDateTime
     * @param date  当前时间
     * @return      LocalDateTime型日期
     */
    private static LocalDateTime convertDateToLDT(Date date) {
        return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * /LocalDateTime转换为Date
     * @param time  当前时间
     * @return      Date型日期
     */
    private static Date convertLDTToDate(LocalDateTime time) {
        return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
    }

    @Test
    public void test1(){
        Date date = new Date();
        LocalDateTime localDateTime = convertDateToLDT(date);
        String timeStr = formatTime(localDateTime,"yyyy-MM-dd HH:mm:ss");
        log.info("call localDateTime timeStr display :{}",timeStr);

        // plus
        LocalDateTime dateTimePlus = plus(localDateTime,2, ChronoUnit.DAYS);
        Date dateTimePlusDate = convertLDTToDate(dateTimePlus);
        log.info("call dateTimePlusDate date display :{}" , dateTimePlusDate);
        log.info("call dateTimePlusDate localDateTime display :{}" , formatTime(dateTimePlus,"yyyy-MM-dd HH:mm:ss"));


        // minus
        LocalDateTime dateTimeMinus = minus(localDateTime,26, ChronoUnit.MINUTES);
        Date dateTimeMinusDate = convertLDTToDate(dateTimeMinus);
        log.info("call dateTimeMinusDate date display :{}" , dateTimeMinusDate);
        log.info("call dateTimeMinusDate localDateTime display :{}" , formatTime(dateTimeMinus,"yyyy-MM-dd HH:mm:ss"));
    }

}

猜你喜欢

转载自blog.csdn.net/caox_nazi/article/details/83302098
今日推荐