1、java 支持日期格式化
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
public static final String DATE_TIME_FORMATTER_PATTERN = "yyyy-MM-dd HH:mm:ss";
public static final String DATE_FORMATTER_PATTERN = "yyyy-MM-dd";
public static final String TIME_FORMATTER_PATTERN = "HH:mm:ss";
public static final SimpleDateFormat S_DATE_TIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static final SimpleDateFormat S_DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd");
public static final SimpleDateFormat S_TIME_FORMATTER = new SimpleDateFormat("HH:mm:ss");
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
2、获取今天日期
Java 8 中的 LocalDate 用于表示当天日期。和 java.util.Date
不同,它只有日期,不包含时间。
LocalDate now = LocalDate.now();
控制台结果输出:
2022-12-25
3、获取年、月、日信息
LocalDate 类提供了获取年、月、日的快捷方法,其实例还包含很多其它的日期属性。通过调用这些方法就可以很方便的得到需要的日期信息,不用像以前一样需要依赖 java.util.Calendar
类了。
LocalDate now = LocalDate.now();
int year = now.getYear();
int month = now.getMonthValue();
int day = now.getDayOfMonth();
System.out.printf("year = %d, month = %d, day = %d", year, month, day);
控制台结果输出:
year = 2022, month = 12, day = 25
4、设置特定日期
在第二个例子中,我们通过静态工厂方法 now()
非常容易地创建了当天日期,你还可以调用另一个有用的工厂方法LocalDate.of()
创建任意日期, 该方法需要传入年、月、日做参数,返回对应的 LocalDate 实例。
LocalDate date = LocalDate.of(2022, 12, 24);
System.out.println(date);
控制台结果输出:
2022-12-24
5、判断两个日期是否相等
LocalDate 重载了 equal 方法:
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2022, 12, 25);
LocalDate dateAgo= LocalDate.of(2022, 12, 24);
if (date.equals(now)) {
System.out.println("是同一天");
}
if (dateAgo.equals(now)) {
System.out.println("不是同一天");
}
6、检查像信用卡还款这种周期性事件
Java 中另一个日期时间的处理就是检查类似每月信用卡还款日期、老婆生日、房贷缴款日或保险缴费日这些周期性事件。如果你在信用卡部门工作,那么一定会有一个模块用来在客户每个还款日期向客户发送提示还款信息。
Java 中如何检查这些节日或其它周期性事件呢?答案就是 MonthDay 类。这个类组合了月份和日,去掉了年,这意味着你可以用它判断每年都会发生事件。和这个类相似的还有一个 YearMonth 类。这些类也都是不可变并且线程安全的值类型。下面我们通过 MonthDay 来检查周期性事件:
LocalDate now = LocalDate.now();
MonthDay repaymentDate= MonthDay.of(dateOfBirth.getMonth(), dateOfBirth.getDayOfMonth());
MonthDay currentMonthDay = MonthDay.from(now);
if (currentMonthDay.equals(dateOfRepayment )) {
System.out.println("today is repayment day");
} else {
System.out.println("Sorry, today is not repayment day");
}
7、获取当前时间
LocalTime 类提供了获取时、分、秒的快捷方法,可以调用静态工厂方法 now()
来获取当前时间。默认的格式是 hh:mm:ss:nnn
。
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
控制台输出:
13:35:56.155
8、在现有的时间上增加小时
增加小时、分、秒来计算将来的时间很常见。Java 8日期和时间类型是:不可变类型和线程安全.
通过plusHours()
方法实现增加小时功能。温馨提示:这些方法返回一个全新的 LocalTime 实例,由于其不可变性,返回后一定要用变量赋值。
LocalTime localTime = LocalTime.now();
System.out.println(localTime);
LocalTime localTime1 = localTime.plusHours(2);//增加2小时
System.out.println(localTime1);
9、计算一周后的日期
LocalDate 日期不包含时间信息,它的 plus()
方法用来增加天、周、月,ChronoUnit 类声明了这些时间单位。由于 LocalDate 也是不变类型,返回后一定要用变量赋值。
LocalDate now = LocalDate.now();
LocalDate plusDate = now.plus(1, ChronoUnit.WEEKS);
System.out.println(now);
System.out.println(plusDate);
10、计算一年前或一年后的日期
利用 LocalDate.minus()
方法计算一年前的日期。
LocalDate now = LocalDate.now();
LocalDate minusDate = now.minus(1, ChronoUnit.YEARS);
LocalDate plusDate1 = now.plus(1, ChronoUnit.YEARS);
System.out.println(minusDate);
System.out.println(plusDate1);
11、使用 Java 8 的 Clock 时钟类
Java 8 增加了一个 Clock 时钟类用于获取当时的时间戳,或当前时区下的日期时间信息。以前用到System.currentTimeInMillis()
和 TimeZone.getDefault()
的地方都可用 Clock 替换。
Clock clock = Clock.systemUTC();
Clock clock1 = Clock.systemDefaultZone();
System.out.println(clock);
System.out.println(clock1);
12、如何用 Java 判断日期是早于还是晚于另一个日期
LocalDate 类有两类方法 isBefore()
和 isAfter()
用于比较日期。调用 isBefore()
方法时,如果给定日期小于当前日期则返回 true。
LocalDate tomorrow = LocalDate.of(2018,6,20);
if(tomorrow.isAfter(now)){
System.out.println("Tomorrow comes after today");
}
LocalDate yesterday = now.minus(1, ChronoUnit.DAYS);
if(yesterday.isBefore(now)){
System.out.println("Yesterday is day before today");
}
13、如何表示信用卡到期这类固定日期
YearMonth 是另一个组合类,用于表示信用卡到期日、FD 到期日、期货期权到期日等。还可以用这个类得到 当月共有多少天,YearMonth 实例的 lengthOfMonth()
方法可以返回当月的天数,在判断 2 月有 28 天还是 29 天时非常有用。
YearMonth currentYearMonth = YearMonth.now();
System.out.printf("Days in month year %s: %d%n", currentYearMonth, currentYearMonth.lengthOfMonth());
YearMonth mortgageDueDate = YearMonth.of(2022, Month.JANUARY);
System.out.printf("Your Mortgage due date on %s %n", mortgageDueDate);
14、在 Java 8 中判断闰年
LocalDate 类有一个很实用的方法 isLeapYear()
判断该实例是否是一个闰年。
15、计算两个日期之间的天数和月数
常见日期操作是计算两个日期之间的天数、周数或月数。在 Java 8 中可以用 java.time.Period
类来做计算。下面这个例子中,我们计算了当天和将来某一天之间的月数。
LocalDate now = LocalDate.now();
LocalDate date = LocalDate.of(2023, Month.MARCH, 20);
Period period = Period.between(now, date);
System.out.println("离下个时间还有" + period.getMonths() + " 个月");
控制台输出:
离下个时间还有2 个月
16、包含时差信息的日期和时间
在 Java 8 中,ZoneOffset 类用来表示时区,举例来说印度与 GMT 或 UTC 标准时区相差 +05:30,可以通过ZoneOffset.of()
静态方法来获取对应的时区。一旦得到了时差就可以通过传入 LocalDateTime 和 ZoneOffset 来创建一个 OffSetDateTime
对象。
LocalDateTime datetime = LocalDateTime.now();
ZoneOffset offset = ZoneOffset.of("+05:30");
OffsetDateTime date = OffsetDateTime.of(datetime, offset);
System.out.println("Date and Time with timezone offset in Java : " + date);
17、在 Java 8 中获取当前的时间戳
Instant 类有一个静态工厂方法 now()
会返回当前的时间戳
Instant timestamp = Instant.now();
System.out.println(timestamp);
18、Java 8 自定义格式化工具解析日期
通过调用 DateTimeFormatter
的 ofPattern()
静态方法并传入任意格式返回其实例,格式中的字符和以前代表的一样,M 代表月,m 代表分。如果格式不规范会抛出 DateTimeParseException
异常,不过如果只是把 M 写成 m 这种逻辑错误是不会抛异常的。
19、在 Java 8 中如何把日期转换成字符串
下面的例子将返回一个代表日期的格式化字符串。和前面类似,还是需要创建 DateTimeFormatter
实例并传入格式,但这回调用的是 format()
方法,而非 parse()
方法。这个方法会把传入的日期转化成指定格式的字符串。
LocalDateTime arrivalDate = LocalDateTime.now();
try {
DateTimeFormatter format = DateTimeFormatter.ofPattern("MMMdd yyyy hh:mm a");
String landing = arrivalDate.format(format);
System.out.printf("Arriving at : %s %n", landing);
}catch (DateTimeException ex) {
System.out.printf("%s can't be formatted!%n", arrivalDate);
ex.printStackTrace();
}
20、Java 8 日期时间API 总结
-
提供了
javax.time.ZoneId
获取时区。 -
提供了 LocalDate 和 LocalTime 类。
-
Java 8 的所有日期和时间 API 都是不可变类并且线程安全,而现有的 Date 和 Calendar API 中的
java.util.Date
和SimpleDateFormat
是非线程安全的。 -
主包是
java.time
, 包含了表示日期、时间、时间间隔的一些类。里面有两个子包java.time.format
用于格式化,java.time.temporal
用于更底层的操作。 -
时区代表了地球上某个区域内普遍使用的标准时间。每个时区都有一个代号,格式通常由区域/城市构成(Asia/Tokyo),在加上与格林威治或 UTC 的时差。例如:东京的时差是 +09:00。
-
OffsetDateTime
类实际上组合了LocalDateTime
类和ZoneOffset
类。用来表示包含和格林威治或 UTC 时差的完整日期(年、月、日)和时间(时、分、秒、纳秒)信息。 -
DateTimeFormatter
类用来格式化和解析时间。与SimpleDateFormat
不同,这个类不可变并且线程安全,需要时可以给静态常量赋值。DateTimeFormatter
类提供了大量的内置格式化工具,同时也允许你自定义。在转换方面也提供了parse()
将字符串解析成日期,如果解析出错会抛出DateTimeParseException
。DateTimeFormatter
类同时还有format()
用来格式化日期,如果出错会抛出DateTimeException
异常。
21、Java8 日期常规操作总结
LocalDateTime与字符串互转/Date互转/LocalDate互转/指定日期/时间比较
字符串互转
DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime time = LocalDateTime.now();
String localTime = df.format(time);
LocalDateTime ldt = LocalDateTime.parse("2022-12-30 10:12:05",df);
System.out.println("LocalDateTime转成String类型的时间:"+localTime);
System.out.println("String类型的时间转成LocalDateTime:"+ldt);
Date转LocalDateTime
java.util.Date date = new java.util.Date();
Instant instant = date.toInstant();
ZoneId zone = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
LocalDateTime 转Date
LocalDateTime localDateTime = LocalDateTime.now();
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDateTime.atZone(zone).toInstant();
java.util.Date date = Date.from(instant);
LocalDateTime与LocalDate互转
LocalDateTime now = LocalDateTime.now();
LocalDate localDate = now.toLocalDate();
LocalDate与Date互转
LocalDate localDate = LocalDate.now();
ZoneId zone = ZoneId.systemDefault();
Instant instant = localDate.atStartOfDay().atZone(zone).toInstant();
java.util.Date date = Date.from(instant);
时间调整
LocalDateTime now = LocalDateTime.now();
//明天
LocalDateTime plusDays = now.plusDays(1);
//昨天
LocalDateTime plusDays2 = now.plusDays(-1);
//还有时分等同理
时间间隔
-
使用Duration进行 day,hour,minute,second等的计算
-
使用Period进行Year,Month的计算
Duration duration = Duration.between(localDateTime,localDateTime4);
duration.toDays();
duration.toHours();
duration.toMinutes();
Period period2 = Period.between(localDateTime.toLocalDate(),localDateTime4.toLocalDate());
period2.getYears();
period2.getMonths();
period2.toTotalMonths();
判断是否是今天或昨天
String time = "2022-12-30 11:20:45";
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime localTime = LocalDateTime.parse(time, dtf);
LocalDateTime startTime = LocalDate.now().atTime(0, 0, 0);
LocalDateTime endTime = LocalDate.now().atTime(23, 59, 59);
LocalDateTime startYesterday = LocalDate.now().plusDays(-1).atTime(0, 0, 0);
LocalDateTime endYesterday = LocalDate.now().plusDays(-1).atTime(23, 59, 59);
//如果小于昨天的开始日期
if (localTime.isBefore(startYesterday)) {
System.out.println("时间是过去");
}
//时间是昨天
if (localTime.isAfter(startYesterday) && localTime.isBefore(endYesterday)) {
System.out.println("时间是昨天");
}
//如果大于今天的开始日期,小于今天的结束日期
if (localTime.isAfter(startTime) && localTime.isBefore(endTime)) {
System.out.println("时间是今天");
}
//如果大于今天的结束日期
if (localTime.isAfter(endTime)) {
System.out.println("时间是未来");
}
}
获取当天的开始和结束时间
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
//获取当前时间
LocalDateTime nowTime = LocalDateTime.now();
//获取当前日期
LocalDate nowDate = LocalDate.now();
//设置零点
LocalDateTime beginTime = LocalDateTime.of(nowDate,LocalTime.MIN);
//将时间进行格式化
String time1= beginTime.format(dtf);
//设置当天的结束时间
LocalDateTime endTime = LocalDateTime.of(nowDate,LocalTime.MAX);
//将时间进行格式化
String time2 =dtf.format(endTime);
System.out.println("今天开始的时间beginTime:"+time1);
System.out.println("今天结束的时间endTime:"+time2);
}
控制台输出: 今天开始的时间beginTime:2022-12-30 00:00:00
今天结束的时间endTime: 2022-12-30 23:59:59
本周开始、结束时间
//本周开始时间
TemporalAdjuster FIRST_OF_WEEK =
TemporalAdjusters.ofDateAdjuster(localDate -> localDate.minusDays(localDate.getDayOfWeek().getValue()- DayOfWeek.MONDAY.getValue()));
String weekStart = df.format(inputDate.with(FIRST_OF_WEEK));
//本周结束时间
TemporalAdjuster LAST_OF_WEEK =
TemporalAdjusters.ofDateAdjuster(localDate -> localDate.plusDays(DayOfWeek.SUNDAY.getValue() - localDate.getDayOfWeek().getValue()));
String weekEnd = df.format(inputDate.with(LAST_OF_WEEK));
本月第一天、最后一天
//TODO 本月的第一天
String monthStart = df.format(LocalDate.of(inputDate.getYear(),inputDate.getMonth(),1));
//TODO 本月的最后一天
String monthEnd = df.format(inputDate.with(TemporalAdjusters.lastDayOfMonth()));
LocalDateTime 生成指定时间段的随机时间
/**
* 取范围日期的随机日期时间,不含边界
* @param startDay
* @param endDay
* @return
*/
public static LocalDateTime randomLocalDateTime(int startDay, int endDay) {
int plusMinus = 1;
if (startDay < 0 && endDay > 0) {
plusMinus = Math.random() > 0.5 ? 1 : -1;
if (plusMinus > 0) {
startDay = 0;
} else {
endDay = Math.abs(startDay);
startDay = 0;
}
} else if (startDay < 0 && endDay < 0) {
plusMinus = -1;
//两个数交换
startDay = startDay + endDay;
endDay = startDay - endDay;
startDay = startDay - endDay;
//取绝对值
startDay = Math.abs(startDay);
endDay = Math.abs(endDay);
}
//指定时间
LocalDate day = LocalDate.now().plusDays(plusMinus * RandomUtils.nextInt(startDay, endDay));
int hour = RandomUtils.nextInt(1, 24);
int minute = RandomUtils.nextInt(0, 60);
int second = RandomUtils.nextInt(0, 60);
LocalTime time = LocalTime.of(hour, minute, second);
return LocalDateTime.of(day, time);
}
方法调用:
//生成指定时间段的随机LocalDateTime时间
randomLocalDateTime(-3, 3).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
或
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime ldt = randomLocalDateTime(-7, 1);
String odMapStartTime = ldt.format(dtf);
String odMapEndTime = ldt.plusMinutes(r.nextInt(5)).format(dtf);
22、Java 日期常规操作踩坑集
用Calendar设置时间的坑
反例:
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR, 10);
System.out.println(c.getTime());
运行结果:
Thu Mar 30 22:28:05 GMT+08:00 2022
解析:
我们设置了10小时,但运行结果是22点,而不是10点。因为Calendar.HOUR
默认是按12小时制处理的,需要使用Calendar.HOUR_OF_DAY
,因为它才是按24小时处理的。
正例:
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, 10);
Java日期格式化YYYY的坑
反例:
Calendar calendar = Calendar.getInstance();
calendar.set(2022, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("YYYY-MM-dd");
System.out.println("2022-12-31 转 YYYY-MM-dd 格式后 " + dtf.format(testDate));
运行结果:
2022-12-31 转 YYYY-MM-dd 格式后 2023-12-31
解析:
为什么明明是2022年12月31号,就转了一下格式,就变成了2023年12月31号了?因为YYYY是基于周来计算年的,它指向当天所在周属于的年份,一周从周日开始算起,周六结束,只要本周跨年,那么这一周就算下一年的了。正确姿势是使用yyyy格式。
正例:
Calendar calendar = Calendar.getInstance();
calendar.set(2022, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println("2022-12-31 转 yyyy-MM-dd 格式后 " + dtf.format(testDate));
Java日期格式化hh的坑
反例:
String str = "2022-12-30 12:00";
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-dd hh:mm");
Date newDate = dtf.parse(str);
System.out.println(newDate);
运行结果:
Wed Mar 30 00:00:00 GMT+08:00 2022
解析:
设置的时间是12点,为什么运行结果是0点呢?因为hh是12制的日期格式,当时间为12点,会处理为0点。正确姿势是使用HH,它才是24小时制。
Calendar获取的月份比实际数字少1即(0-11)
反例:
//获取当前月,当前是12月
Calendar calendar = Calendar.getInstance();
System.out.println("当前"+calendar.get(Calendar.MONTH)+"月份");
运行结果:
当前11月份
解析:
The first month of the year in the Gregorian and Julian calendars
is <code>JANUARY</code> which is 0;
也就是1月对应的是下标 0,依次类推。因此获取正确月份需要加 1.
正例:
//获取当前月,当前是12月
Calendar calendar = Calendar.getInstance();
System.out.println("当前"+(calendar.get(Calendar.MONTH)+1)+"月份");
Java日期格式化DD的坑
反例:
Calendar calendar = Calendar.getInstance();
calendar.set(2022, Calendar.DECEMBER, 31);
Date testDate = calendar.getTime();
SimpleDateFormat dtf = new SimpleDateFormat("yyyy-MM-DD");
System.out.println("2022-12-31 转 yyyy-MM-DD 格式后 " + dtf.format(testDate));
运行结果:
2022-12-31 转 yyyy-MM-DD 格式后 2022-12-365
解析:
DD和dd表示的不一样,DD表示的是一年中的第几天,而dd表示的是一月中的第几天,所以应该用的是dd。
SimleDateFormat的format初始化问题
反例:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf.format(20221231));
运行结果:
1970-01-01
解析:
用format格式化日期是,要输入的是一个Date类型的日期,而不是一个整型或者字符串。
日期本地化问题
反例:
String dateStr = "Wed Mar 18 10:00:00 2020";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy");
LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
System.out.println(dateTime);
运行结果:
Exception in thread "main" java.time.format.DateTimeParseException: Text 'Wed Mar 18 10:00:00 2020' could not be parsed at index 0
at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949)
at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1851)
at java.time.LocalDateTime.parse(LocalDateTime.java:492)
at com.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)
解析:
DateTimeFormatter
这个类默认进行本地化设置,如果默认是中文,解析英文字符串就会报异常。可以传入一个本地化参数(Locale.US
)解决这个问题
正例:
String dateStr = "Wed Mar 18 10:00:00 2020";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy",Locale.US);
LocalDateTime dateTime = LocalDateTime.parse(dateStr, formatter);
System.out.println(dateTime);
SimpleDateFormat 解析的时间精度问题
反例:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String time = "2022-12";
System.out.println(sdf.parse(time));
运行结果:
Exception in thread "main" java.text.ParseException: Unparseable date: "2022-12"
at java.text.DateFormat.parse(DateFormat.java:366)
at com.example.demo.SynchronizedTest.main(SynchronizedTest.java:19)
解析:
SimpleDateFormat 可以解析长于/等于它定义的时间精度,但是不能解析小于它定义的时间精度。
SimpleDateFormat 的线性安全问题
反例:
public class SimpleDateFormatTest {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000));
while (true) {
threadPoolExecutor.execute(() -> {
String dateString = sdf.format(new Date());
try {
Date parseDate = sdf.parse(dateString);
String dateString2 = sdf.format(parseDate);
System.out.println(dateString.equals(dateString2));
} catch (ParseException e) {
e.printStackTrace();
}
});
}
}
运行结果:
Exception in thread "pool-1-thread-49" java.lang.NumberFormatException: For input string: "5151."
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.example.demo.SimpleDateFormatTest.lambda$main$0(SimpleDateFormatTest.java:19)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-47" java.lang.NumberFormatException: For input string: "5151."
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.parseLong(Long.java:631)
at java.text.DigitList.getLong(DigitList.java:195)
at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.example.demo.SimpleDateFormatTest.lambda$main$0(SimpleDateFormatTest.java:19)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
解析: 全局变量的SimpleDateFormat,在并发情况下,存在安全性问题。
-
SimpleDateFormat
继承了 DateFormat -
DateFormat
类中维护了一个全局的Calendar变量 -
sdf.parse(dateStr)
和sdf.format(date)
,都是由Calendar引用来储存的。 -
如果
SimpleDateFormat
是static全局共享的,Calendar引用也会被共享。 -
又因为Calendar内部并没有线程安全机制,所以全局共享的
SimpleDateFormat
不是线性安全的。
解决SimpleDateFormat
线性不安全问题,有三种方式:
-
将
SimpleDateFormat
定义为局部变量 -
使用
ThreadLocal
。 -
方法加同步锁
synchronized
。
正例:
public class SimpleDateFormatTest {
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>();
public static DateFormat getDateFormat() {
DateFormat df = threadLocal.get();
if(df == null){
df = new SimpleDateFormat(DATE_FORMAT);
threadLocal.set(df);
}
return df;
}
public static String formatDate(Date date) throws ParseException {
return getDateFormat().format(date);
}
public static Date parse(String strDate) throws ParseException {
return getDateFormat().parse(strDate);
}
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1000));
while (true) {
threadPoolExecutor.execute(() -> {
try {
String dateString = formatDate(new Date());
Date parseDate = parse(dateString);
String dateString2 = formatDate(parseDate);
System.out.println(dateString.equals(dateString2));
} catch (ParseException e) {
e.printStackTrace();
}
});
}
}
}