Java8中LocalDate详解Date线程不安全的原因

news2025/1/12 23:13:14

LocalDate

分类分工

java.time.LocalDate ->只对年月日做出处理
java.time.LocalTime ->只对时分秒纳秒做出处理
java.time.LocalDateTime ->同时可以处理年月日和时分秒

优点

除了使用起来更加简单和灵活,主要是传统的时期处理类Date、Calendar不是多线程安全的,而LocalDate 线程安全的,所以不用担心并发问题。

实际使用

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
 
import com.google.common.collect.Lists;
 
/**
 * Java 8 的时间工具类
 */
public class DateUtils {
 
    /**
     * 默认使用系统当前时区
     */
    private static final ZoneId ZONE = ZoneId.systemDefault();
 
    private static final String DATE_FORMAT = "yyyy-MM-dd";
 
    private static final String DATE_FORMAT_DS = "yyyyMMdd";
 
    private static final String DATE_FORMAT_DEFAULT = "yyyy-MM-dd HH:mm:ss";
 
    private static final String TIME_FORMAT = "yyyyMMddHHmmss";
 
    private static final String REGEX = "\\:|\\-|\\s";
 
    public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
    /**
     * 获取当前时间
     *
     * @param format
     * @return
     */
    public static String getCurrentTime(String format) {
 
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
 
        LocalDateTime now = LocalDateTime.now();
 
        return now.format(dateTimeFormatter);
 
    }
 
    /**
     * 获取昨日时间
     *
     * @param format
     * @return
     */
    public static String getYesterday(String format) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate nowDate = LocalDate.now();
        LocalDate yesterday = nowDate.minusDays(1);
        return yesterday.format(dateTimeFormatter);
 
    }
 
    /**
     * 获取上周的时间
     *
     * @param format
     * @return
     */
    public static String getLastWeek(String format) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate nowDate = LocalDate.now();
        LocalDate lastWeek = nowDate.minusWeeks(1);
        return lastWeek.format(dateTimeFormatter);
    }
 
    /**
     * 获取上个月的时间
     *
     * @param format
     * @return
     */
    public static String getLastMonth(String format) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate nowDate = LocalDate.now();
        LocalDate lastMonth = nowDate.minusMonths(1);
        return lastMonth.format(dateTimeFormatter);
 
    }
 
    /**
     * 获取去年的时间
     *
     * @param format
     * @return
     */
    public static String getLastYear(String format) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate nowDate = LocalDate.now();
        LocalDate lastYear = nowDate.minusYears(1);
        return lastYear.format(dateTimeFormatter);
    }
 
    /**
     * 获取前多少天的日期
     *
     * @param format
     * @param num
     * @return
     */
    public static String getBeforeSomeDay(String format, int num) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate nowDate = LocalDate.now();
        LocalDate beforeDay = nowDate.minusDays(num);
        return beforeDay.format(dateTimeFormatter);
    }
 
    /**
     * 获取指定时间的前多少天
     *
     * @param format
     * @param date
     * @param num
     * @return
     */
    public static String getBeforeDayOfDate(String format, String date, int num) {
 
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);
        LocalDate beforeDay = localDate.minusDays(num);
        return beforeDay.format(dateTimeFormatter);
 
    }
 
    /**
     * 获取当天的开始时间  yyyy-MM-dd 00:00:00
     *
     * @param format
     * @return
     */
    public static String getDayStartTime(String format, String date) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);
        LocalDateTime toDayStart = LocalDateTime.of(localDate, LocalTime.MIN);
        return toDayStart.format(FORMATTER);
    }
 
    /**
     * 获取当天的结束时间 yyyy-MM-dd 23:59:59
     *
     * @param format
     * @return
     */
    public static String getDayEndTime(String format, String date) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);
        LocalDate localDate = LocalDate.parse(date, dateTimeFormatter);
        LocalDateTime toDayStart = LocalDateTime.of(localDate, LocalTime.MAX);
        return toDayStart.format(FORMATTER);
    }
 
    /**
     * 获取两个时间之间的间隔天数
     *
     * @param startDate yyyyMMdd
     * @param endDate   yyyyMMdd
     * @return
     */
    public static long getRangeCountOfDate(String startDate, String endDate) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT_DS);
        LocalDate startLocalDate = LocalDate.parse(startDate, dateTimeFormatter);
        LocalDate endLocalDate = LocalDate.parse(endDate, dateTimeFormatter);
        long count = ChronoUnit.DAYS.between(startLocalDate, endLocalDate);
        return count;
 
    }
 
    /**
     * 后期两个时间之间的所有日期 【包含开始时间和结束时间】
     *
     * @param startDate yyyyMMdd
     * @param endDate   yyyyMMdd
     * @return
     */
    public static List<String> getRangeOfDate(String startDate, String endDate) {
        List<String> range = Lists.newArrayList();
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_FORMAT_DS);
        LocalDate startLocalDate = LocalDate.parse(startDate, dateTimeFormatter);
        LocalDate endLocalDate = LocalDate.parse(endDate, dateTimeFormatter);
        long count = ChronoUnit.DAYS.between(startLocalDate, endLocalDate);
        if (count < 0) {
            return range;
        }
 
        range = Stream.iterate(startLocalDate, d -> d.plusDays(1)).limit(count + 1).map(
            s -> s.format(dateTimeFormatter)).collect(Collectors.toList());
        return range;
    }
 
    public static void main(String[] args) {
        System.out.println(getRangeOfDate("20191010", "20191020"));
    }
 
}

Date线程不安全的原因

1- SimpleDateFormat线程安全问题

使用ExecutorService提交多个任务的方式,模拟并发环境将字符串转换为日期即测试parse方法,代码如下:

@Test
public void testParse() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    List<String> dateStrList = Lists.newArrayList(
            "2018-04-01 10:00:01",
            "2018-04-02 11:00:02",
            "2018-04-03 12:00:03",
            "2018-04-04 13:00:04",
            "2018-04-05 14:00:05"
    );
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    for (String str : dateStrList) {
        executorService.execute(() -> {
            try {
                simpleDateFormat.parse(str);
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

运行后,报错如下:
在这里插入图片描述
可见并发环境下使用SimpleDateFormat的parse方法有线程安全问题!

线程安全问题的原因:

在SimpleDateFormat转换日期是通过Calendar对象来操作的,SimpleDateFormat继承DateFormat类,DateFormat类中维护一个Calendar对象,代码如下:
在这里插入图片描述
在这里插入图片描述
通过DateFormat类中的注释可知:此处Calendar实例被用来进行日期-时间计算,既被用于format方法也被用于parse方法!

在parse方法的最后,会调用CalendarBuilder的establish方法,入参就是SimpleDateFormat维护的Calendar实例,在establish方法中会调用calendar的clear方法,如下:
在这里插入图片描述
可知SimpleDateFormat维护的用于format和parse方法计算日期-时间的calendar被清空了,如果此时线程A将calendar清空且没有设置新值,线程B也进入parse方法用到了SimpleDateFormat对象中的calendar对象,此时就会产生线程安全问题!

2- 解决方案

**每一个使用SimpleDateFormat对象进行日期-时间进行format和parse方法的时候就创建一个新的SimpleDateFormat对象,用完就销毁即可!**代码如下:

/**
 * 模拟并发环境下使用SimpleDateFormat的parse方法将字符串转换成Date对象
 */
@Test
public void testParseThreadSafe() {
    ExecutorService executorService = Executors.newCachedThreadPool();
    List<String> dateStrList = Lists.newArrayList(
            "2018-04-01 10:00:01",
            "2018-04-02 11:00:02",
            "2018-04-03 12:00:03",
            "2018-04-04 13:00:04",
            "2018-04-05 14:00:05"
    );
    for (String str : dateStrList) {
        executorService.execute(() -> {
            try {
                //创建新的SimpleDateFormat对象用于日期-时间的计算
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
                simpleDateFormat.parse(str);
                TimeUnit.SECONDS.sleep(1);
                simpleDateFormat = null; //销毁对象
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

在运行可以发现不会报出之前的错误了!

综上所述,使用SimpleDateFormat对象进行日期-时间计算时,如果SimpleDateFormat是多个线程共享的就会有线程安全问题!应该让每一个线程都有一个独立的SimpleDateFormat对象用于日期-时间的计算!此时就可以使用ThreadLocal将SimpleDateFormat绑定到线程上,是的该线程上的日期-时间计算顺序的使用SimpleDateFormat对象,这样也可以避免线程安全问题!

另外就是使用LocalDateTime

LocalDateTime now = LocalDateTime.of(LocalDate.now(), LocalTime.now());
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
String dateStr = now.format(fmt);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/120.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

刷爆leetcode第十二期 0026 数组中数字出现的次数

编号0026 数组中数字出现的次数 一个整型数组 nums 里除两个数字之外&#xff0c;其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n)&#xff0c;空间复杂度是O(1)。 题目示例如下 这里其实是一道我一个月之前做的题目 在学弟的博客里刚好看…

【数据结构与算法】Java实现七大排序算法汇总

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【数据结构与算法】 ✈️✈️本篇内容: Java实现七大排序算法汇总&#xff01; &#x1f680;&#x1f680;由于本篇博客涉及代码较多&#xff0c;博主把代码都提…

刷爆leetcode第十一期 0023~0025

刷爆leetcode第十一期 编号0023 相同的树编号0024 对称二叉树编号0025 另一个树的子树编号0023 相同的树 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是…

多旋翼无人机仿真 rotors_simulator:用键盘控制无人机飞行

多旋翼无人机仿真 rotors_simulator&#xff1a;用键盘控制无人机飞行前言书接上文接口测试键盘指令发布指令转换与发布修改 rotors_simulator 的控制接口节点测试前言 RotorS 是一个MAV gazebo 仿真系统。 提供了几种多旋翼仿真模型&#xff0c;例如 AscTec HummingbirdAsc…

PHP反序列化

序列化与反序列化 序列化 反序列是指把对象转换为字符串的过程&#xff0c;便于在内存、文件、数据库中保存、传输&#xff0c;PHP中使用serialize函数进行序列化。 <?phpclass Person{public $name"php";protected $id;private $age;}$a new Person();$a_se…

全排列笔记

14天阅读挑战赛 全排列 题目 给定一个 没有重复 数字的序列&#xff0c;返回其所有可能的全排列。 示例: 输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ] 解答 方法一&#xff1a;回溯 思路 从高中的数学知识我们可以知道 从[1,2,3…

如何在Linux上优雅地写代码-Linux生存指南

初入Linux&#xff0c;发现老是要面对一个命令行&#xff0c;大黑框&#xff0c;看不懂各种手册&#xff0c;写代码也是用vi/vim&#xff0c;难受的捉急。其实Linux下的各种工具&#xff0c;强大得超出你的想象&#xff0c;如果你初入Linux&#xff0c;那么你急需阅读这篇文章&…

操作系统的主要功能

目录 一. 处理机管理功能 1.1 进程控制 1.2 进程同步 1.3 进程通信 1.4 进程调度 二. 存储器管理功能 2.1 内存分配 2.2 内存保护 2.3 地址映射 2.4 内存扩充 三. 设备管理功能 3.1 缓冲管理 3.2 设备分配 3.3 设备处理 3.4 设备独立性和虚拟设备 四…

关于Python爬虫兼职,这里有一条高效路径

前言 昨天&#xff0c;一位00后前来报喜&#xff0c;也表达感谢。 他说&#xff0c;当初刚毕业啥也不会也找不到工作&#xff0c;最后听了我的&#xff0c;边学爬虫边做兼职项目&#xff0c;积极主动求职投简历&#xff0c;既可以兼职获得收益&#xff0c;也能积累项目经验谋求…

Linux:以K、M、G查看文件大小;

简介&#xff1a;灵活多变的查看文件的大小 历史攻略&#xff1a; Linux&#xff1a;sudo免密 python&#xff1a;执行dos命令、Linux命令 案例源码&#xff1a; # 以适当方式显示文件大小&#xff1a; ls -lh# 以byte显示文件大小&#xff1a; ls -l# 以M显示文件大小&am…

NR PUSCH(五) DMRS

微信同步更新欢迎关注同名modem协议笔记 PUSCH DMRS和PDSCH DMRS内容基本一样&#xff0c;但也有不同的地方&#xff0c;例如PUSCH 可能需要Transform precoding&#xff0c;port 对应0~11(DMRS configured type2)等等。先简单看看Transformprecoding的相关内容&#xff0c;Tr…

Excel数据分析实战之开宗明义: Excel与数据分析实战

大家好&#xff0c;我是爱编程的喵喵。双985硕士毕业&#xff0c;现担任全栈工程师一职&#xff0c;热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。喜欢通过博客创作的方式对所学的知识进行总结…

军用大数据 - Spark机器学习

文章目录第1关&#xff1a;Iris 分类任务描述相关知识1&#xff1a;观察数据集2&#xff1a;RFormula 特征提取3&#xff1a;pandas 的 concat 函数编程要求代码实现————————————————————————————————————————第2关&#xff1a;图片识…

网络原理 --- 传输层Ⅲ TCP协议中的滑动窗口,流量控制和拥塞控制

文章目录网络原理传输层TCP协议4.滑动窗口5.流量控制6.拥塞控制总结网络原理 介绍TCP/IP协议中每一层里面的核心内容~ 应用层传输层网络层数据链路层物理层 传输层TCP协议 4.滑动窗口 TCP能够保证可靠传输,但是失去了效率! 但是TCP希望能够在保证可靠性的前提下,尽可能地提…

达梦数据库在不修改SQL的情况下为SQL指定HINT

前言 在Oracle中可以使用outline、SQL PROFILE等手段去在无需修改SQL语句的情况下&#xff0c;来保证SQL执行计划在不同硬件环境下相同&#xff0c;从而保证SQL语句在不同环境的执行效率。那么&#xff0c;在达梦数据库中则可以使用SF_INJECT_HINT系统函数达到类似的效果。 SF…

Java学习笔记 --- 异常

一、基本介绍 Java语言中&#xff0c;将程序执行中发生的不正常情况称为“异常”。&#xff08;开发过程中的语法错误和逻辑错误不是异常&#xff09; 执行过程中所发生的异常事件可以分为两类 1、Error&#xff08;错误&#xff09;&#xff1a;Java虚拟机无法解决的严重问…

十月了,请问2022届的同学们都找到工作了吗?

今年的就业大环境就不多说了&#xff0c;大家都知道。一边是超千万规模的应届毕业生&#xff0c;叠加教培、地产等行业裁员&#xff1b;另一边则是疫情反复影响之下&#xff0c;企业瘦身裁员、停招、缩招。在白领性质的劳动力市场&#xff0c;劳动力供给严重大于需求&#xff0…

【C语言】解题训练

目录 字符串左旋 方法1 方法2 字符串旋转结果判断 方法1 方法2 杨氏矩阵 位段 题目1 题目2 联合体 题目1 题目2 有序序列合并 变种水仙花 找单身狗 字符串左旋 实现一个函数&#xff0c;可以左旋字符串中的k个字符。 例如&#xff1a; ABCD左旋一个字符得到…

纷享销客联合B.P商业伙伴携手30+企业CEO走进南天信息

数字化智能化建设的当下&#xff0c;数字化服务商承担着承上启下的核心力量。企业数字化转型成为刚需&#xff0c;意味着ICT企业的市场前景持续乐观&#xff0c;但在疫情和竞争加剧之下&#xff0c;企业发展也遭遇增长的挑战&#xff0c;如何在数字中国的趋势之下&#xff0c;乘…

大学网课搜题公众号系统

大学网课搜题公众号系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xf…