【JAVA8新的时间与日期 API】- 传统时间格式化的线程安全问题
Java8之前的日期和时间API,存在一些问题,最重要的就是线程安全的问题。这些问题都在Java8中的日期和时间API中得到了解决,而且Java8中的日期和时间API更加强大。
传统时间格式化的线程安全问题
示例:
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*; public class TestOldSimpleDateFormat {
public static void main(String[] args) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
return sdf.parse("2020-01-01");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i=0;i<10;i++){
Future<Date> future = pool.submit(task);
list.add(future);
}
for (Future<Date> future : list){
System.out.println(future.get());
} pool.shutdown();
}
}
以上代码运行会报错:

报错缘由:取部分源码解释
/**
* SimpleDateFormat 类的 parse 方法 部分源码
*/
public Date parse(String source) throws ParseException
{
ParsePosition pos = new ParsePosition(0);
Date result = parse(source, pos);
if (pos.index == 0)
throw new ParseException("Unparseable date: \"" + source + "\"" ,
pos.errorIndex);
return result;
} public Date parse(String text, ParsePosition pos)
{
// 省略上面诸多代码
Date parsedDate; CalendarBuilder calb = new CalendarBuilder();
try {
//这里这个 calendar 对象是 SimpleDateFormat 类的父类 DateFormat 中的属性 : protected Calendar calendar;
parsedDate = calb.establish(calendar).getTime();//这个 calb.establish(calendar) 方法中,这个方法中的主要步骤不是原子操作,并且会对 calendar 对象进行修改,所以在多线程环境下就会出现线程安全问题。
// 省略下面面诸多代码
}
catch (IllegalArgumentException e) {
//省略.........................
return null;
}
return parsedDate;
}
Calendar establish(Calendar cal) {
boolean weekDate = isSet(WEEK_YEAR)
&& field[WEEK_YEAR] > field[YEAR];
if (weekDate && !cal.isWeekDateSupported()) {
// Use YEAR instead
if (!isSet(YEAR)) {
set(YEAR, field[MAX_FIELD + WEEK_YEAR]);
}
weekDate = false;
}
cal.clear();
// Set the fields from the min stamp to the max stamp so that
// the field resolution works in the Calendar.
for (int stamp = MINIMUM_USER_STAMP; stamp < nextStamp; stamp++) {
for (int index = 0; index <= maxFieldIndex; index++) {
if (field[index] == stamp) {
cal.set(index, field[MAX_FIELD + index]);
break;
}
}
}
if (weekDate) {
int weekOfYear = isSet(WEEK_OF_YEAR) ? field[MAX_FIELD + WEEK_OF_YEAR] : 1;
int dayOfWeek = isSet(DAY_OF_WEEK) ?
field[MAX_FIELD + DAY_OF_WEEK] : cal.getFirstDayOfWeek();
if (!isValidDayOfWeek(dayOfWeek) && cal.isLenient()) {
if (dayOfWeek >= 8) {
dayOfWeek--;
weekOfYear += dayOfWeek / 7;
dayOfWeek = (dayOfWeek % 7) + 1;
} else {
while (dayOfWeek <= 0) {
dayOfWeek += 7;
weekOfYear--;
}
}
dayOfWeek = toCalendarDayOfWeek(dayOfWeek);
}
cal.setWeekDate(field[MAX_FIELD + WEEK_YEAR], weekOfYear, dayOfWeek);
}
return cal;
}
综上,我们可以看到 SimpleDateFormat 类中的parse 方法,调用了 CalendarBuilder 的 establish(calendar) 方法,并在方法中,对 calendar 对象进行了各种判断及修改,并且这些操作都不是原子操作或同步操作,而这个calendar 对象又是 SimpleDateFormat 的父类 DateFormat 的一个实例变量,所以,在多线程同时调用SimpleDateFormat 的 parse 方法的时候,就会出现线程安全问题。
针对以上异常,JAVA8之前的解决办法:
1. 将 SimpleDateFormat 对象定义成局部变量。
2. 加锁。
3. 使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。
示例(加锁):
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Callable<Date> task = new Callable<Date>() {
@Override
public synchronized Date call() throws Exception {//加个同步,解决问题
return sdf.parse("2020-01-01");
// return DateFormatThreadLocal.convert("2020-01-01");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i=0;i<10;i++){
Future<Date> future = pool.submit(task);
list.add(future);
}
for (Future<Date> future : list){
System.out.println(future.get());
}
pool.shutdown();
示例(ThreadLocal):
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date; public class DateFormatThreadLocal {
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static Date convert(String source) throws Exception {
return df.get().parse(source);
}
} //////////////////////////////////////////////////////////////// public class TestOldSimpleDateFormat {
public static void main(String[] args) throws Exception {
// SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Callable<Date> task = new Callable<Date>() {
@Override
public Date call() throws Exception {
// return sdf.parse("2020-01-01");
return DateFormatThreadLocal.convert("2020-01-01");
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<Date>> list = new ArrayList<>();
for (int i=0;i<10;i++){
Future<Date> future = pool.submit(task);
list.add(future);
}
for (Future<Date> future : list){
System.out.println(future.get());
}
pool.shutdown();
} }
JAVA8的解决办法:使用新的API(DateTimeFormatter 和 LocalDate )
示例:
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*; public class TestOldSimpleDateFormat {
public static void main(String[] args) throws Exception {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); Callable<LocalDate> task = new Callable<LocalDate>() {
@Override
public LocalDate call() throws Exception {
// return sdf.parse("2020-01-01");
return LocalDate.parse("2020-01-01",formatter);
}
};
ExecutorService pool = Executors.newFixedThreadPool(10);
List<Future<LocalDate>> list = new ArrayList<>();
for (int i=0;i<10;i++){
Future<LocalDate> future = pool.submit(task);
list.add(future);
}
for (Future<LocalDate> future : list){
System.out.println(future.get());
}
pool.shutdown();
}
}
【JAVA8新的时间与日期 API】- 传统时间格式化的线程安全问题的更多相关文章
- 为什么不建议使用Date,而是使用Java8新的时间和日期API?
Java 8:新的时间和日期API 在Java 8之前,所有关于时间和日期的API都存在各种使用方面的缺陷,因此建议使用新的时间和日期API,分别从旧的时间和日期的API的缺点以及解决方法.Java ...
- 【转】Java 8新特性(四):新的时间和日期API
Java 8另一个新增的重要特性就是引入了新的时间和日期API,它们被包含在java.time包中.借助新的时间和日期API可以以更简洁的方法处理时间和日期. 在介绍本篇文章内容之前,我们先来讨论Ja ...
- Java 8新特性(四):新的时间和日期API
Java 8另一个新增的重要特性就是引入了新的时间和日期API,它们被包含在java.time包中.借助新的时间和日期API可以以更简洁的方法处理时间和日期. 在介绍本篇文章内容之前,我们先来讨论Ja ...
- Linux 设置系统时间和日期 API
嵌入式Linux 设置时间和日期 API ,它是busybox要提取的源代码. Linux设置时间和日期的步骤: 1. 设置系统时间和日期: 2. 该系统的时间和日期,同步到硬件. #include ...
- Java8新特性 - 新时间和日期 API
本地时间和时间戳 主要方法: now:静态方法,根据当前时间创建对象 of:静态方法,根据指定日期/时间创建对象 plusDays,plusWeeks,plusMonths,plusYears:向当前 ...
- [转]JavaSE 8—新的时间和日期API
为什么我们需要一个新的时间日期API Java开发中一直存在一个问题,JDK提供的时间日期API一直对开发者没有提供良好的支持. 比如,已有的的类(如java.util.Date和SimpleDate ...
- jdk8-全新时间和日期api
1.jdk8日期和时间api是线程安全的 1.java.time 处理日期时间 2.java.time.temporal: 时间校正器.获取每个月第一天,周几等等 3.java.time.forma ...
- Java8 中的时间和日期 API
1. 日期和时间概述 LocalDate,LocalTime,LocalDateTime类的实例是不可变的对象,分别表示使用 ISO-8601 日历系统 的日期,时间,日期和时间;它们提供了简单的日期 ...
- java8新特性七-Date Time API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理. 在旧版的 Java 中,日期时间 API 存在诸多问题,其中有: 非线程安全 − java.ut ...
随机推荐
- Postman+Newman+Git+Jenkins接口自动化测试
一.Postman 1.创建Collection,在Collection中创建接口请求,如下图所示. 2.编写接口对应的断言Test和Pre-request Script,如下图所示. 3.配置接口 ...
- Codeforces Round #635 (Div. 2)部分(A~E)题解
虽然打的是div1,但最后半小时完全处于挂机状态,不会做1C,只有个 \(O(n^3)\) 的想法,水了水论坛,甚至看了一下div2的AB,所以干脆顺便写个div2的题解吧,内容看上去还丰富一些(X) ...
- HTML、CSS与JS实现简易iPhone计算器
效果如图 源码,通俗易懂 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...
- 题解 CF1348D 【Phoenix and Science】
题目大意,每天细菌会在早上选择分裂,晚上生长. 观察题目,我们可以发现.不管我们怎么分裂细菌,这一天晚上的总质量都是前一天晚上的总质量加上今天的细菌数. 那么我们肯定希望细菌分裂的越多越好,这样我们减 ...
- Redis 入门到分布式 (一)Redis初识
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.Redis特性目录 Redis的特性: 速度快 持久化 多种数据结构 支持多种编辑语言 功能丰富 简 ...
- Java实现 LeetCode 257 二叉树的所有路径
257. 二叉树的所有路径 给定一个二叉树,返回所有从根节点到叶子节点的路径. 说明: 叶子节点是指没有子节点的节点. 示例: 输入: 1 / \ 2 3 \ 5 输出: ["1->2 ...
- java实现 洛谷 P1464 Function
import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.S ...
- Java实现 蓝桥杯 算法提高最小方差生成树
1 问题描述 给定带权无向图,求出一颗方差最小的生成树. 输入格式 输入多组测试数据.第一行为N,M,依次是点数和边数.接下来M行,每行三个整数U,V,W,代表连接U,V的边,和权值W.保证图连通.n ...
- Java实现无向图的欧拉回路判断问题
1 问题描述 Problem Description 欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路.现给定一个图,问是否存在欧拉回路? Input 测试输入包含若干测试 ...
- PAT 挖掘机技术哪家强
为了用事实说明挖掘机技术到底哪家强,PAT 组织了一场挖掘机技能大赛.现请你根据比赛结果统计出技术最强的那个学校. 输入格式: 输入在第 1 行给出不超过 105 的正整数 N,即参赛人数.随后 N ...