【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 ...
随机推荐
- 移动端适配-rem(新)
概念 对于移动端开发来说,无可避免的就是直面各种设备不同分辨率和不同DPR(设备像素比)的问题,在此忽略其他兼容性问题的探讨. 移动端像素 设备像素(dp),也叫物理像素.指设备能控制显示的最小物理单 ...
- vc程序设计--图形绘制1
利用绘图函数创建填充区.Windows通过使用当前画笔画一个图形的边界,然后用当前的刷子填充这个图形来创建-一个填充图形.共有三个填充图形,第一个是用深灰色画刷填充带圆角的矩形,第二个是采用亮 ...
- php CURL 发送请求封装
cURL可以使用URL的语法模拟浏览器来传输数据,因为它是模拟浏览器,因此它同样支持多种协议,FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 以及 ...
- 《学习scrapy框架爬小说》的进一步完善
一.完善目标: 1.为方便使用,把小说拼音或英文名,小说输出中文名,第一章节url地址变量化,修改这些参数即可爬取不同的小说. 2.修改settings.py设置文件,配置为记录debug的log信息 ...
- js函数prototype属性学习(二)
继续探讨js对象的prototype属性,前面已经看到在创建完一个对象之后,随时都会有一个_proto_属性伴随所有,那么,这个_proto_又是用来干嘛的,面试时问的高大上的原型链又是怎么回事? 拿 ...
- Python编程思想(3):数字及其相关运算
Python 提供了三种数值类型:int(整型),float(浮点型)和complex(复数). int:通常被称为整型或者整数,如200.299.10都属于整型: float:浮点数包含整数和小数部 ...
- Java实现 蓝桥杯VIP 算法训练 幂方分解
问题描述 任何一个正整数都可以用2的幂次方表示.例如: 137=27+23+20 同时约定方次用括号来表示,即ab 可表示为a(b). 由此可知,137可表示为: 2(7)+2(3)+2(0) 进一步 ...
- Java实现蓝桥杯算法提高12-2扑克排序
扑克牌排序 问题描述 扑克牌排序:构造扑克牌数组,对扑克牌进行排序. 排序原则如下:数字从小到大是2-10.J.Q.K和A,花色从小到大是方块(diamond).梅花(club).红桃(heart). ...
- Java实现 LeetCode 189 旋转数组
189. 旋转数组 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数. 示例 1: 输入: [1,2,3,4,5,6,7] 和 k = 3 输出: [5,6,7,1,2,3,4] ...
- AS中将module转成library的步骤
转换步骤是在Android Studio 2.3版本下进行的,其他版本未测试 将要变成library的module的gradle文件的第一行 修改前:apply plugin: 'com.Androi ...