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】- 传统时间格式化的线程安全问题的更多相关文章

  1. 为什么不建议使用Date,而是使用Java8新的时间和日期API?

    Java 8:新的时间和日期API 在Java 8之前,所有关于时间和日期的API都存在各种使用方面的缺陷,因此建议使用新的时间和日期API,分别从旧的时间和日期的API的缺点以及解决方法.Java ...

  2. 【转】Java 8新特性(四):新的时间和日期API

    Java 8另一个新增的重要特性就是引入了新的时间和日期API,它们被包含在java.time包中.借助新的时间和日期API可以以更简洁的方法处理时间和日期. 在介绍本篇文章内容之前,我们先来讨论Ja ...

  3. Java 8新特性(四):新的时间和日期API

    Java 8另一个新增的重要特性就是引入了新的时间和日期API,它们被包含在java.time包中.借助新的时间和日期API可以以更简洁的方法处理时间和日期. 在介绍本篇文章内容之前,我们先来讨论Ja ...

  4. Linux 设置系统时间和日期 API

    嵌入式Linux 设置时间和日期 API ,它是busybox要提取的源代码. Linux设置时间和日期的步骤: 1. 设置系统时间和日期: 2. 该系统的时间和日期,同步到硬件. #include ...

  5. Java8新特性 - 新时间和日期 API

    本地时间和时间戳 主要方法: now:静态方法,根据当前时间创建对象 of:静态方法,根据指定日期/时间创建对象 plusDays,plusWeeks,plusMonths,plusYears:向当前 ...

  6. [转]JavaSE 8—新的时间和日期API

    为什么我们需要一个新的时间日期API Java开发中一直存在一个问题,JDK提供的时间日期API一直对开发者没有提供良好的支持. 比如,已有的的类(如java.util.Date和SimpleDate ...

  7. jdk8-全新时间和日期api

    1.jdk8日期和时间api是线程安全的 1.java.time  处理日期时间 2.java.time.temporal: 时间校正器.获取每个月第一天,周几等等 3.java.time.forma ...

  8. Java8 中的时间和日期 API

    1. 日期和时间概述 LocalDate,LocalTime,LocalDateTime类的实例是不可变的对象,分别表示使用 ISO-8601 日历系统 的日期,时间,日期和时间;它们提供了简单的日期 ...

  9. java8新特性七-Date Time API

    Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理. 在旧版的 Java 中,日期时间 API 存在诸多问题,其中有: 非线程安全 − java.ut ...

随机推荐

  1. 03 . Nginx日志配置及日志切割

    Nginx日志 日志对于统计排错来说是非常有利的,Nginx日志主要分为两种: access_log(访问日志)和error_log(错误日志),通过访问日志可以得到用户的IP地址.浏览器的信息,请求 ...

  2. Ratel源码-C/S事件梳理

    一.Ratel介绍 Ratel 是一个可以在命令行中玩斗地主的项目,可以使用小巧的jar包在拥有JVM环境的终端中进行游戏,同时支持人人对战和人机对战两种模式,丰富你的空闲时间! 二.玩法Demo 三 ...

  3. Nginx 笔记(三)nginx 配置实例 - 反向代理、负载均衡、动静分离

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.反向代理 反向代理准备工作: (1)在 liunx 系统安装 tomcat,使用默认端口 8080 ...

  4. CentOS 虚拟机 下载及 搭建

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) CentOS 虚拟机安装包下载 : 链接:https://pan.baidu.com/s/1JDIASm ...

  5. Spring Cloud Ribbon 客户端负载均衡

    Ribbon客户端组件提供一系列完善的配置选项,比如连接超时.重试.重试算法等,内置可插拔.可定制的负载均衡组件.下面是用到的一些负载均衡策略: 简单轮询负载均衡 加权轮询负载均衡 区域感知轮询负载均 ...

  6. Java实现 LeetCode 726 原子的数量(递归+HashMap处理)

    726. 原子的数量 给定一个化学式formula(作为字符串),返回每种原子的数量. 原子总是以一个大写字母开始,接着跟随0个或任意个小写字母,表示原子的名字. 如果数量大于 1,原子后会跟着数字表 ...

  7. excel表 更改后缀名 xlsx转成csv csv转换xlsx

    excel 转成CSV 步骤: 1.打开你需要转换的Excel表格.这里特别介绍一下多个数字的现象,我们知道,像身份证这些多位数字,在正常表格里都是会加上一个'来使其显示的,或者换成"文本& ...

  8. 用斗地主的实例学会使用java Collections工具类

    目录 一.背景 二.概念 1.定义 2.方法 2.1.排序方法 2.2.查找/替换方法 三.斗地主实例 3.1.代码结构 3.2.常量定义 3.3.单只牌类 3.4.玩家类 3.5.主程序 四.深入理 ...

  9. iOS-Reactive Cocoa的常见用法

    今天是周末,临近年底,工作上遇到不可抗力,会有点一些变动!这多少会让人有一点静不下来,但需克制,Reactive Cocoa是今天的主角! 废话不多说,今天聊聊Reactive Cocoa的常见使用! ...

  10. Go语言json编码驼峰转下划线、下划线转驼峰

    目录 一.需求 二.实现 三.使用 JsonSnakeCase统一转下划线json JsonSnakeCase统一转驼峰json 一.需求 golang默认的结构体json转码出来,都是大写驼峰的,并 ...