版权声明:本文为博主原创文章,未经博主允许不得转载。 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"));
}
}