Java8中LocalDate详解Date线程不安全的原因
创始人
2024-01-13 01:42:57

LocalDate

分类分工

java.time.LocalDate ->只对年月日做出处理
java.time.LocalTime ->只对时分秒纳秒做出处理
java.time.LocalDateTime ->同时可以处理年月日和时分秒

优点

除了使用起来更加简单和灵活,主要是传统的时期处理类Date、Calendar不是多线程安全的,而LocalDate 线程安全的,所以不用担心并发问题。

实际使用

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;import com.google.common.collect.Lists;/*** Java 8 的时间工具类*/
public class DateUtils {/*** 默认使用系统当前时区*/private static final ZoneId ZONE = ZoneId.systemDefault();private static final String DATE_FORMAT = "yyyy-MM-dd";private static final String DATE_FORMAT_DS = "yyyyMMdd";private static final String DATE_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";private static final String TIME_FORMAT = "yyyyMMddHHmmss";private static final String REGEX = "\\:|\\-|\\s";public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");/*** 获取当前时间** @param format* @return*/public static String getCurrentTime(String format) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDateTime now = LocalDateTime.now();return now.format(dateTimeFormatter);}/*** 获取昨日时间** @param format* @return*/public static String getYesterday(String format) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate nowDate = LocalDate.now();LocalDate yesterday = nowDate.minusDays(1);return yesterday.format(dateTimeFormatter);}/*** 获取上周的时间** @param format* @return*/public static String getLastWeek(String format) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate nowDate = LocalDate.now();LocalDate lastWeek = nowDate.minusWeeks(1);return lastWeek.format(dateTimeFormatter);}/*** 获取上个月的时间** @param format* @return*/public static String getLastMonth(String format) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate nowDate = LocalDate.now();LocalDate lastMonth = nowDate.minusMonths(1);return lastMonth.format(dateTimeFormatter);}/*** 获取去年的时间** @param format* @return*/public static String getLastYear(String format) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate nowDate = LocalDate.now();LocalDate lastYear = nowDate.minusYears(1);return lastYear.format(dateTimeFormatter);}/*** 获取前多少天的日期** @param format* @param num* @return*/public static String getBeforeSomeDay(String format, int num) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate nowDate = LocalDate.now();LocalDate beforeDay = nowDate.minusDays(num);return beforeDay.format(dateTimeFormatter);}/*** 获取指定时间的前多少天** @param format* @param date* @param num* @return*/public static String getBeforeDayOfDate(String format, String date, int num) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);LocalDate beforeDay = localDate.minusDays(num);return beforeDay.format(dateTimeFormatter);}/*** 获取当天的开始时间  yyyy-MM-dd 00:00:00** @param format* @return*/public static String getDayStartTime(String format, String date) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);LocalDateTime toDayStart = LocalDateTime.of(localDate, LocalTime.MIN);return toDayStart.format(FORMATTER);}/*** 获取当天的结束时间 yyyy-MM-dd 23:59:59** @param format* @return*/public static String getDayEndTime(String format, String date) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);LocalDateTime toDayStart = LocalDateTime.of(localDate, LocalTime.MAX);return toDayStart.format(FORMATTER);}/*** 获取两个时间之间的间隔天数** @param startDate yyyyMMdd* @param endDate   yyyyMMdd* @return*/public static long getRangeCountOfDate(String startDate, String endDate) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT_DS);LocalDate startLocalDate = LocalDate.parse(startDate, dateTimeFormatter);LocalDate endLocalDate = LocalDate.parse(endDate, dateTimeFormatter);long count = ChronoUnit.DAYS.between(startLocalDate, endLocalDate);return count;}/*** 后期两个时间之间的所有日期 【包含开始时间和结束时间】** @param startDate yyyyMMdd* @param endDate   yyyyMMdd* @return*/public static List getRangeOfDate(String startDate, String endDate) {List range = Lists.newArrayList();DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT_DS);LocalDate startLocalDate = LocalDate.parse(startDate, dateTimeFormatter);LocalDate endLocalDate = LocalDate.parse(endDate, dateTimeFormatter);long count = ChronoUnit.DAYS.between(startLocalDate, endLocalDate);if (count < 0) {return range;}range = Stream.iterate(startLocalDate, d -> d.plusDays(1)).limit(count + 1).map(s -> s.format(dateTimeFormatter)).collect(Collectors.toList());return range;}public static void main(String[] args) {System.out.println(getRangeOfDate("20191010", "20191020"));}}

Date线程不安全的原因

1- SimpleDateFormat线程安全问题

使用ExecutorService提交多个任务的方式,模拟并发环境将字符串转换为日期即测试parse方法,代码如下:

@Test
public void testParse() {ExecutorService executorService = Executors.newCachedThreadPool();List dateStrList = Lists.newArrayList("2018-04-01 10:00:01","2018-04-02 11:00:02","2018-04-03 12:00:03","2018-04-04 13:00:04","2018-04-05 14:00:05");SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");for (String str : dateStrList) {executorService.execute(() -> {try {simpleDateFormat.parse(str);TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}});}
}

运行后,报错如下:
在这里插入图片描述
可见并发环境下使用SimpleDateFormat的parse方法有线程安全问题!

线程安全问题的原因:

在SimpleDateFormat转换日期是通过Calendar对象来操作的,SimpleDateFormat继承DateFormat类,DateFormat类中维护一个Calendar对象,代码如下:
在这里插入图片描述
在这里插入图片描述
通过DateFormat类中的注释可知:此处Calendar实例被用来进行日期-时间计算,既被用于format方法也被用于parse方法!

在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法,如下:
在这里插入图片描述
可知SimpleDateFormat维护的用于format和parse方法计算日期-时间的calendar被清空了,如果此时线程A将calendar清空且没有设置新值,线程B也进入parse方法用到了SimpleDateFormat对象中的calendar对象,此时就会产生线程安全问题!

2- 解决方案

**每一个使用SimpleDateFormat对象进行日期-时间进行format和parse方法的时候就创建一个新的SimpleDateFormat对象,用完就销毁即可!**代码如下:

/*** 模拟并发环境下使用SimpleDateFormat的parse方法将字符串转换成Date对象*/
@Test
public void testParseThreadSafe() {ExecutorService executorService = Executors.newCachedThreadPool();List dateStrList = Lists.newArrayList("2018-04-01 10:00:01","2018-04-02 11:00:02","2018-04-03 12:00:03","2018-04-04 13:00:04","2018-04-05 14:00:05");for (String str : dateStrList) {executorService.execute(() -> {try {//创建新的SimpleDateFormat对象用于日期-时间的计算SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");simpleDateFormat.parse(str);TimeUnit.SECONDS.sleep(1);simpleDateFormat = null; //销毁对象} catch (Exception e) {e.printStackTrace();}});}
}

在运行可以发现不会报出之前的错误了!

综上所述,使用SimpleDateFormat对象进行日期-时间计算时,如果SimpleDateFormat是多个线程共享的就会有线程安全问题!应该让每一个线程都有一个独立的SimpleDateFormat对象用于日期-时间的计算!此时就可以使用ThreadLocal将SimpleDateFormat绑定到线程上,是的该线程上的日期-时间计算顺序的使用SimpleDateFormat对象,这样也可以避免线程安全问题!

另外就是使用LocalDateTime

LocalDateTime now = LocalDateTime.of(LocalDate.now(), LocalTime.now());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
String dateStr = now.format(fmt);

相关内容

热门资讯

demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
阿西吧是什么意思 阿西吧相当于... 即使你没有受到过任何外语培训,你也懂四国语言。汉语:你好英语:Shit韩语:阿西吧(아,씨발! )日...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...