前言
金三银四又有战况:
我们的看官,不能白白牺牲!
现在,立刻,马上,跟我开始复现 ! 开始看我源码分析! 开始了解怎么解决!
正文
复现代码
多线程操作使用SimpleDateFormat ,来进行 时间格式转换, 将时间字符串格式 转换 成 Date 。
10个线程(量小模拟,多点几次才能出现并发情况)
开搞:
public static void main(String[] args) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
try {
String str1 = "2022-01-1" + new Random().nextInt(10);
Date result1 = simpleDateFormat.parse(str1);
System.out.println(Thread.currentThread().getName() + "时间str: " + str1 + ", 转换对应的date :" + result1.toString());
} catch (ParseException e) {
throw new RuntimeException(e);
}
});
thread.start();
}
}
一顿狂点, 看看线程不安全的 SimpleDateFormat 出来的丑态:
‘’不安全‘’的现场
① 报错了
② 出来了混乱的数据 (线程少,多点几次大并发的时候才能出来这些乱数据状况)
为什么会这样?
其实线程不安全,万变不离其中,就是有公共资源没有隔离使用好,多线程操作的时候,公共资源错乱了。
ok ,拒绝纸上谈兵,我们源码谈兵:
源码分析
simpleDateFormat.parse(str1) ,点进去看看源码:
parse(source, pos) ,点进去看看源码:
可以看到最后返回的是 Date parsedDate ,来至于 罪魁祸首 calb.establish(calendar)
到这一步,其实敏感的人已经知道问题了。
因为 calendar 是 紫色的
就是说 是个公共的资源,看一眼什么来头:
共用的:
接着继续点calb.establish(calendar) ,看看源码:
共用的东西,不做线程安全隔离,自然就不安全了。
顺带看看我们也常用的 format函数:
同样也是用了公共的 calendar :
那怎么安全?
①上锁
每个线程都给我一一排队等锁来运行 parse()
评价: synchronized 加锁后的多线程=串行执行锁住的代码块,线程阻塞,执行效率低,不推荐。
②一个线程一个SimpleDateFormat
既然 我们代码用的simpleDateFormat 是共用的, 所以里面的calendar 是同一个共用的。
我们要是不想让calendar是公共的,我们创建多个就行。
所以每个线程,我们单独给它new 一个 simpleDateFormat ,不就解决了么。
评价: 一个线程new一个,一万个线程一万个,创建、销毁,资源消耗大,不推荐。
③ 使用ThreadLocal ,使用ThreadLocal存储每个线程拥有的SimpleDateFormat对象的副本,互相不干扰。
评价: 不错。推荐!
④ 使用java 8 可以说是 专门 针对 这个 线程不安全的 SimpleDateFormat 搞的 DateTimeFormatter 来处理。
private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
String str1 = "2022-01-1" + new Random().nextInt(10);
LocalDate localDate = LocalDate.parse(str1, formatter);
Date result1 = Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
System.out.println(Thread.currentThread().getName() + "时间str: " + str1 + ", 转换对应的date :" + result1.toString());
});
thread.start();
}
}
同样如果是需要date转字符串:
String dateStr = formatter.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(result1.getTime()), ZoneId.systemDefault()));
好了,该篇就到这。