最近在高并发作业下,对于时间的使用,自己从网上做了一些总结
1.说下System.currentTimeMillis()的异常:高并发场景下,它比创建一个普通对象要耗时的多,所以最好不要直接使用
优化方案:使用线程池
public class SystemClock {
private static final String THREAD_NAME = "system.clock";
private static final SystemClock MILLIS_CLOCK = new SystemClock(1);
private final long precision;
private final AtomicLong now;
private SystemClock(long precision) {
this.precision = precision;
now = new AtomicLong(System.currentTimeMillis());
scheduleClockUpdating();
}
public static SystemClock millisClock() {
return MILLIS_CLOCK;
}
private void scheduleClockUpdating() {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, THREAD_NAME);
thread.setDaemon(true);
return thread;
});
scheduler.scheduleAtFixedRate(() ->
now.set(System.currentTimeMillis()), precision, precision, TimeUnit.MILLISECONDS);
}
public long now() {
return now.get();
}
}
以上代码转载路径: https://blog.csdn.net/qq_38011415/article/details/82813299
目前我项目中已使用此种方式。
2. Date 如果不格式化SimpleDateFormat,那么可读性就很差: Tue Sep 10 09:34:04 CST 2019
但SimpleDateFormat
是线程不安全的,SimpleDateFormat
的format
方法最终调用代码:
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date);
boolean useDateFormatSymbols = useDateFormatSymbols();
for (int i = 0; i < compiledPattern.length; ) {
int tag = compiledPattern[i] >>> 8;
int count = compiledPattern[i++] & 0xff;
if (count == 255) {
count = compiledPattern[i++] << 16;
count |= compiledPattern[i++];
}
switch (tag) {
case TAG_QUOTE_ASCII_CHAR:
toAppendTo.append((char)count);
break;
case TAG_QUOTE_CHARS:
toAppendTo.append(compiledPattern, i, count);
i += count;
break;
default:
subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
break;
}
}
return toAppendTo;
}
calendar
是共享变量,并且这个共享变量没有做线程安全控制。当多个线程同时使用相同的SimpleDateFormat对象【如用static
修饰的SimpleDateFormat
】调用format
方法时,多个线程会同时调用calendar.setTime
方法,可能一个线程刚设置好time
值另外的一个线程马上把设置的time
值给修改了导致返回的格式化时间可能是错误的。在多并发情况下使用SimpleDateFormat
需格外注意SimpleDateFormat
除了format
是线程不安全以外,parse
方法也是线程不安全的。parse
方法实际调用alb.establish(calendar).getTime()
方法来解析,alb.establish(calendar)
方法里主要完成
重置日期对象cal的属性值
使用calb中中属性设置cal
返回设置好的cal对象
但是这三步不是原子操作
多线程并发如何保证线程安全 - 避免线程之间共享一个SimpleDateFormat
对象,每个线程使用时都创建一次SimpleDateFormat
对象
=> 创建和销毁对象的开销大 - 对使用format
和parse
方法的地方进行加锁
=> 线程阻塞性能差 - 使用ThreadLocal
保证每个线程最多只创建一次SimpleDateFormat
对象
=> 较好的方法
Date
对时间处理比较麻烦,比如想获取某年、某月、某星期,以及n
天以后的时间,如果用Date
来处理的话真是太难了,你可能会说Date
类不是有getYear
、getMonth
这些方法吗,获取年月日很Easy
,但都被弃用了啊
因为我当前项目已经是jdk1.8之上了,所以就使用LocalDate来处理时间吧。
3.LocalDate
LocalDate localDate = LocalDate.now();
//构造指定的年月日
LocalDate localDate1 = LocalDate.of(2019, 9, 10);
获取年、月、日、星期几
int year = localDate.getYear();
int year1 = localDate.get(ChronoField.YEAR);
Month month = localDate.getMonth();
int month1 = localDate.get(ChronoField.MONTH_OF_YEAR);
int day = localDate.getDayOfMonth();
int day1 = localDate.get(ChronoField.DAY_OF_MONTH);
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
int dayOfWeek1 = localDate.get(ChronoField.DAY_OF_WEEK);
LocalTime 只会获取几点几分几秒
LocalDateTime 获取年月日时分秒,等于LocalDate+LocalTime
Instant获取秒数
Instant instant = Instant.now();
long currentSecond = instant.getEpochSecond();
获取毫秒数:
long currentMilli = instant.toEpochMilli();
LocalDate
、LocalTime
、LocalDateTime
、Instant
为不可变对象,修改这些对象对象会返回一个副本
- 增加、减少年数、月数、天数等 以
LocalDateTime
为例LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56); //增加一年 localDateTime = localDateTime.plusYears(1); localDateTime = localDateTime.plus(1, ChronoUnit.YEARS); //减少一个月 localDateTime = localDateTime.minusMonths(1); localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS); 复制代码
- 通过
with
修改某些值//修改年为2019 localDateTime = localDateTime.withYear(2020); //修改为2022 localDateTime = localDateTime.with(ChronoField.YEAR, 2022); 复制代码
还可以修改月、日
时间计算
比如有些时候想知道这个月的最后一天是几号、下个周末是几号,通过提供的时间和日期API可以很快得到答案
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.with(firstDayOfYear());
复制代码
比如通过firstDayOfYear()
返回了当前日期的第一天日期,还有很多方法这里不在举例说明
格式化时间
LocalDate localDate = LocalDate.of(2019, 9, 10);
String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
//自定义格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String s3 = localDate.format(dateTimeFormatter);
复制代码
DateTimeFormatter
默认提供了多种格式化方式,如果默认提供的不能满足要求,可以通过DateTimeFormatter
的ofPattern
方法创建自定义格式化方式
解析时间
LocalDate localDate1 = LocalDate.parse("20190910", DateTimeFormatter.BASIC_ISO_DATE);
LocalDate localDate2 = LocalDate.parse("2019-09-10", DateTimeFormatter.ISO_LOCAL_DATE);
复制代码
和SimpleDateFormat
相比,DateTimeFormatter
是线程安全的
链接:https://juejin.cn/post/6844903939402383368
所以现在我时间操作,都是使用的LocalDateTime,比较Date的功能他都有。
上面已经了解了Date的坑
4.Calendar
5.SimpleDateFormat的坑,推荐看一下https://www.cnblogs.com/jay-huaxiao/p/12591272.html
正反例写的极详尽
所以总结了这几个时间处理格式,还是使用jdk8的 LocalDateTime 是最优选择!