目录
1.Date
2.SimpleDateFormat
3.ThreadLocal的简单说明
4.java8中的日期时间类(LocalDate/LocalTime/LocalDateTime)
5.java8中的日期格式转换
6.java8中的时区处理
是java8以前的日期相关的处理类。
空参构造:这个时间就表示电脑中的当前时间
Date date = new Date();
有参构造:从时间原点开始,过了指定毫秒的那个时间
Date date = new Date(0L);//从时间原点开始,过了0毫秒 ;因为我们在中国,在东八区 需要+8小时
@Test
public void test5() throws ParseException {
// 1.Date用于获取系统时间
// 获取当前的系统时间
Date date = new Date();
System.out.println(date);
// 从时间原点开始,过了0毫秒 ;因为我们在中国,在东八区 需要+8小时
Date date1 = new Date(0l);
System.out.println(date1);
// 又加了一个小时
Date date2 = new Date(3600l * 1000);
System.out.println(date2);
// 时间戳,对于相同的文件名,避免覆盖,可以再前面添加时间戳来标记,也可以标明不同时间的请求,用途很广泛
long l = System.currentTimeMillis();
System.out.println(l);
// 也可以通过date对象获取当前的毫秒值
long time = date.getTime();
// 也可以通过date对象来设置当前日期的毫秒值
date.setTime(0l);
System.out.println(date);
// 可以通过compare方法比较大小
System.out.println(date1.compareTo(date2));
}
Thu Nov 09 19:39:54 CST 2023
Thu Jan 01 08:00:00 CST 1970
Thu Jan 01 09:00:00 CST 1970
1699529994453
Thu Jan 01 08:00:00 CST 1970
-1
一个与语言环境相关的格式化日期和分析日期的工具类。
利用该类可以将日期转换成文本,或者将文本转换成日期。
在使用SimpleDateFormat时需要指定一个需要的格式(pattern)来格式日期(Date).
在此请注意几个字母大小写的差异:
大写的H为24小时制表示一天中的小时数(0-23)
小写的h为12小时制表示一天中的小时数(1-12)
大写的M表示年中的月份
小写的m表示小时中的分钟数
大写的S表示毫秒数
小写的s表示秒数
所以最常用的24小时制的具体日期的pattern为:
yyyy-MM-dd HH:mm:ss
通过format方法将日期转换为字符串,通过parse方法将字符串转换为日期。
@Test
public void test6() throws ParseException {
Date date = new Date();
SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 日期转换字符串,转换后的字符串格式和SimpleDateFormat中的pattern相同
String format11 = format1.format(date);
System.out.println(format11);
SimpleDateFormat format2 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
// 日期转换字符串,转换后的字符串格式和SimpleDateFormat中的pattern相同
String format22 = format2.format(date);
System.out.println(format22);
// 将文本转换为日期,文本格式要和SimpleDateFormat中的pattern相同,否则会出现异常
Date parse1 = format1.parse(format11);
System.out.println(parse1);
Date parse2 = format2.parse(format22);
System.out.println(parse2);
Date parse3 = format1.parse("2023-11-09 12:12:12");
Date parse4 = format1.parse("2023-11-09 14:13:13");
// 计算时间的差值
long cha = parse4.getTime() - parse3.getTime();
// 相差小时数
System.out.println(cha/(1000*60*60));
// 比较时间大小
System.out.println(parse3.compareTo(parse4));
System.out.println(parse4.compareTo(parse3));
}
SimpleDateFormat不是线程安全的,因为SimpleDateFormat类内部有一个Calendar对象引用,它用来储存和这个SimpleDateFormat相关的日期信息。这样就会导致一个问题,如果SimpleDateFormat是static的, 那么多个thread之间就会共享SimpleDateFormat, 同时也是共享Calendar引用。
SimpleDateFormat的parse方法:
Calendar是用来承载字符串转化成日期对象的容器,calendar对象有个clear后set值的过程,高并发下,set值的过程,会出现把上次set值给覆盖的情况。
public class Simple extends Thread{
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
private String name;
private String dateStr;
public Simple(String name, String dateStr){
this.name = name;
this.dateStr = dateStr;
}
@Override
public void run() {
try {
Date parse = simpleDateFormat.parse(dateStr);
System.out.println(parse);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.execute(new Simple("1","2023-01-02"));
executorService.execute(new Simple("2","2023-05-02"));
executorService.shutdown();
}
上述示例中,是在多线程执行的任务中存在日期的格式化处理,打印出来的结果有三种,有可能就是给每个线程任务传入的日期,有可能是其他线程传入的日期,有可能会报错。
解决方案:
(1)将SimpleDateFormat定义成局部变量,但是每调用一次方法意味创建一个SimpleDateFormat对象,浪费内存。
(2)方法加同步锁synchronized,在同一时刻,只有一个线程可以执行类中的某个方法。这样性能较差,每次都要等待锁释放后其他线程才能进入
(3)使用ThreadLocal:每个线程拥有自己的SimpleDateFormat对象。如下所示
public class Simple extends Thread{
private static ThreadLocal simpleDateFormat = new ThreadLocal<>();
private String name;
private String dateStr;
public Simple(String name, String dateStr){
this.name = name;
this.dateStr = dateStr;
}
@Override
public void run() {
try {
SimpleDateFormat format = simpleDateFormat.get();
format = new SimpleDateFormat("yyyy-MM-dd");
Date parse = format.parse(dateStr);
System.out.println(parse);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public class Simple extends Thread{
private static ThreadLocal threadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
private String name;
private String dateStr;
public Simple(String name, String dateStr){
this.name = name;
this.dateStr = dateStr;
}
@Override
public void run() {
try {
DateFormat format = threadLocal.get();
Date parse = format.parse(dateStr);
System.out.println(parse);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
以上两种方法都可以。
使用 ThreadLocal
, 也是将共享变量变为独享,会为每个线程创建一个变量副本,每个线程使用自己的副本的值。线程独享肯定能比方法独享在并发环境中能减少不少创建对象的开销。如果对性能要求比较高的情况下,一般推荐使用这种方法。注意使用完之后,调用remove方法,移除当前线程绑定的局部变量,使内存得到回收。
ThreadLocal的简单说明
ThreadLocal和Synchonized都用于解决多线程并发访问同一个资源对象的时候,可能就会出现线程不安全的问题。
ThreadLocal是与一个线程绑定的本地变量,也就意味着这个变量是线程独有的,是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。
加锁方式(synchronized、Lock) 用于在多个线程间通信时能够获得数据共享。
但是,ThreadLocal 这种解决多线程安全问题的方式与加锁方式(synchronized、Lock) 是有本质的区别。
Synchronized是通过加锁的方式让多个线程逐一访问共享资源。锁是用时间换空间的做法,加锁的方法或代码块同一时间只能被一个线程访问。
ThreadLocal是空间换时间的做法,是为每个线程创建一个副本,每个线程访问自己线程的副本,各个线程之间互相不会干扰,隔离了各个线程之间的数据共享。
根据使用场景的不同,我们可以选择不同的技术手段,关键还是要看你的应用场景对于资源的管理是需要多线程之间共享
还是单线程内部独享
。
多线程共享资源使用加锁的方式,单线程内部独享使用threadlocal方式。
总而言之,threadlocal适用每个线程独享自己的实例,可以在多个方法中共享,但不能在多个线程中共享。
@Test
public void test7() {
LocalDate localDate = LocalDate.now();
System.out.println("日期:" + localDate);
System.out.println("年:" + localDate.getYear());
System.out.println("月:" + localDate.getMonthValue());
System.out.println("在月中的日:" + localDate.getDayOfMonth());
System.out.println("在周中的日:" + localDate.getDayOfWeek().getValue());
System.out.println("在年中的日:" + localDate.getDayOfYear());
LocalTime localTime = LocalTime.now();
System.out.println("时间:" + localTime.toString());
System.out.println("小时:" + localTime.getHour());
System.out.println("分钟:" + localTime.getMinute());
System.out.println("秒:" + localTime.getSecond());
System.out.println("纳秒:" + localTime.getNano());
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime.toString());
}
执行结果:
日期:2023-11-12
年:2023
月:11
在月中的日:12
在周中的日:7
在年中的日:316
时间:09:07:58.763
小时:9
分钟:7
秒:58
纳秒:763000000
2023-11-12T09:07:58.763
日期的修改及加减
@Test
public void test8() {
// 修改,修改后会生成新的对象,原来的对象内容不变
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println("修改前:" + localDateTime);
LocalDateTime localDateTime1 = localDateTime.withYear(2024).withMonth(12).withDayOfMonth(12).withHour(12).withMinute(25).withSecond(58).withNano(25);
System.out.println("修改后:" + localDateTime1);
// 加
LocalDateTime localDateTime2 = LocalDateTime.now();
System.out.println("修改前:" + localDateTime2);
LocalDateTime localDateTime3 = localDateTime2.plusYears(2).plusMonths(2).plusDays(10).plusHours(2).plusMinutes(2).plusSeconds(2);
System.out.println("修改后:" + localDateTime3);
// 减
LocalDateTime localDateTime4 = LocalDateTime.now();
System.out.println("修改前:" + localDateTime4);
LocalDateTime localDateTime5 = localDateTime4.minusYears(1).minusMonths(2).minusDays(2).minusHours(2).minusMinutes(2).minusSeconds(2);
System.out.println("修改后:" + localDateTime5);
}
执行结果
修改前:2023-11-12T09:11:51.626
修改后:2024-12-12T12:25:58.000000025
修改前:2023-11-12T09:11:51.626
修改后:2026-01-22T11:13:53.626
修改前:2023-11-12T09:11:51.626
修改后:2022-09-10T07:09:49.626
注意:在进行日期时间修改和加减的时候,原来的L.ocalDate对象是不会被修改,每次操作都是返回了一个新的LocalDate对象,所以在多线程场景下是数据安全的。
日期的比较
@Test
public void test9() {
// 日期的比较
LocalDate date1 = LocalDate.now();
System.out.println(date1);
LocalDate date2 = LocalDate.parse("2021-01-01", DateTimeFormatter.ISO_LOCAL_DATE);
System.out.println(date2);
System.out.println(date1.isAfter(date2));
System.out.println(date1.isBefore(date2));
System.out.println(date1.compareTo(date2));
}
执行结果
2023-11-12
2021-01-01
true
false
2
ava 8 提供了新的日期时间 API,其中包括用于日期时间格式化的 DateTimeFormatter
,它与 SimpleDateFormat
最大的区别在于:DateTimeFormatter
是线程安全的,而 SimpleDateFormat
并不是线程安全。
@Test
public void test10() {
// 日期转换为字符串
// 使用系统默认格式
LocalDateTime date1 = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
String formatDate1 = date1.format(formatter);
System.out.println(formatDate1);
DateTimeFormatter formatter2 = DateTimeFormatter.ISO_LOCAL_DATE;
String formatDate11 = date1.format(formatter2);
System.out.println(formatDate11);
// 使用指定格式
LocalDateTime date2 = LocalDateTime.now();
DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDate2 = date2.format(formatter1);
System.out.println(formatDate2);
// 字符串转换为日期
LocalDateTime dated3 = LocalDateTime.parse("2023-11-11T11:06:51.084", formatter);
System.out.println(dated3);
LocalDateTime date4 = LocalDateTime.parse("2024-05-01 12:25:25", formatter1);
System.out.println(date4);
}
执行结果:
2023-11-12T09:16:23.132
2023-11-12
2023-11-12 09:16:23
2023-11-11T11:06:51.084
2024-05-01T12:25:25
计算执行时间
@Test
public void test11() throws InterruptedException {
Instant start = Instant.now();
Thread.sleep(10000);
Instant end = Instant.now();
System.out.println(Duration.between(start, end).toMillis());
System.out.println(Duration.between(start, end).getSeconds());
}
执行结果:
10001
10
这是java1.8的新特性,java1.8之前是没有对时区进行处理。
LocalDate、LocalTime、LocalDateTime是不带时区的。
带时区的日期时间类分别为: ZonedDate、ZonedTime、zonedDateTime。其中每个时区都对应着ID,ID的格式为“区域/城市”。例如︰ Asia/Shanghai等。