当前位置: 首页 > news >正文

并发场景使用SimpleDateFormat异常问题和解决

SimpleDateFormat类主要是负责日期的格式化与转换操作,因为它不是线程安全的,所以使用SimpleDateFormat时,务必确保同一个SimpleDateFormat对象不要与其他线程共享,否则并发情况下会出现问题

目录

    • 异常示例
    • 解决方案1:创建了多个SimpleDateFormat类的实例
    • 解决方案2:使用ThreadLocal类
    • 解决方案3:使用DateTimeFormatter处理(JDK1.8)

异常示例

演示一下SimpleDateFormat在多线程情况下报错问题,先创建一个线程,在run方法中转换日期

public class ErrorTest extends Thread{
    private final SimpleDateFormat sdf;
    private final String date;
    public ErrorTest(SimpleDateFormat sdf, String date) {
        super();
        this.sdf = sdf;
        this.date = date;
    }
    @Override
    public void run() {
        try {
            String newDate = sdf.format(sdf.parse(date));
            if (!newDate.equals(date)) {
                System.out.println(this.getName() + "报错了! " +
                        "日期:" + date + " 转换成了:" + newDate);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

写完发现在idea开发中就已经开始报警告了

在这里插入图片描述

下面在启动类中同时传多个日期去转换

public class Run {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        String[] dateArray = new String[] { "2023-01-01", "2023-01-02",
                "2023-01-03", "2023-01-04", "2023-01-05", "2023-01-06", "2023-01-07"};
        int length = dateArray.length;
        ErrorTest[] array = new ErrorTest[length];
        for (int i = 0; i < length; i++) {
            array[i] = new ErrorTest(sdf, dateArray[i]);
        }
        for (int i = 0; i < length; i++) {
            array[i].start();
        }
    }
}

输出:

启动后可以看到控制台报错了,或者是日期转换成了其他莫名其妙的数据,说明SimpleDateFormat在多线程下极易出现日期转换错误的问题

解决方案1:创建了多个SimpleDateFormat类的实例

创建一个类,在每次执行线程时new一个新的SimpleDateFormat对象,让每一个线程享用一个SimpleDateFormat对象

public class SolveTest1 extends Thread{
    private final String date;
    public SolveTest1(String date) {
        super();
        this.date = date;
    }
    @Override
    public void run() {
        try {
        	// 转换
            Date dateD = new SimpleDateFormat("yyyy-MM-dd").parse(date);
            // 打印
            String newDate = new SimpleDateFormat("yyyy-MM-dd").format(dateD);
            if (newDate.equals(date)) {
                System.out.println(this.getName() + "成功了! " +
                        "日期:" + date + " 转换成了:" + newDate);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

启动类不再传SimpleDateFormat对象

/**
* ThreadLocal
*/
public class Run {
    public static void main(String[] args) {
        String[] dateArray = new String[] { "2023-01-01", "2023-01-02",
                "2023-01-03", "2023-01-04", "2023-01-05", "2023-01-06", "2023-01-07"};
        int length = dateArray.length;
        SolveTest1[] array = new SolveTest1[length];
        for (int i = 0; i < length; i++) {
            array[i] = new SolveTest1(dateArray[i]);
        }
        for (int i = 0; i < length; i++) {
            array[i].start();
        }
    }
}

输出可以看到七次都转换成功了

在这里插入图片描述

解决方案2:使用ThreadLocal类

ThreadLocal简介:每个Thread线程内部都有一个ThreadLocalMap,以线程作为key,要储存的值作为value,所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。这种变量在多线程环境下通过getset方法访问时能保证各个线程的变量相对独立于其他线程内的变量

下面创建一个类来使用ThreadLocal,记得使用完要调用remove()方法回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。

/**
* ThreadLocal
*/
public class SolveTest2 extends Thread{
    private final String date;
    private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<>();

    public SolveTest2(String date) {
        super();
        this.date = date;
    }
    @Override
    public void run() {
        try {
            // 转换
            Date dateD = getSimpleDateFormat("yyyy-MM-dd").parse(date);
            // 打印
            String newDate = getSimpleDateFormat("yyyy-MM-dd").format(dateD);
            if (newDate.equals(date)) {
                System.out.println(this.getName() + "成功了! " +
                        "日期:" + date + " 转换成了:" + newDate);
            }
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 获取SimpleDateFormat
     * @param dateFormat 日期格式
     * @return SimpleDateFormat
     */
    public static SimpleDateFormat getSimpleDateFormat(String dateFormat) {
        SimpleDateFormat sdf = THREAD_LOCAL.get();
        if (sdf == null) {
            sdf = new SimpleDateFormat(dateFormat);
            THREAD_LOCAL.set(sdf);
        }
        // 回收
        THREAD_LOCAL.remove();
        return sdf;
    }
}

启动类和解决方案1的启动类代码一样,只需把调用线程改一下就行了。

在这里插入图片描述
可以看到执行结果也都成功了

解决方案3:使用DateTimeFormatter处理(JDK1.8)

在JDK1.8只后出来了DateTimeFormatter,和SimpleDateFormat不同的是,DateTimeFormatter是线程安全的,他是不变对象,可以只创建一个实例,到处引用。

public class SolveTest3 extends Thread{

    private final String date;
    /**
     * 定义DateTimeFormatter格式
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    public SolveTest3(String date) {
        super();
        this.date = date;
    }
    @Override
    public void run() {
        // 转换
        LocalDate localDate = LocalDate.parse(date, DATE_TIME_FORMATTER);
        // 打印
        String newDate = localDate.format(DATE_TIME_FORMATTER);
        if (newDate.equals(date)) {
            System.out.println(this.getName() + "成功了! " +
                    "日期:" + date + " 转换成了:" + newDate);
        }
    }
}

启动类同前面方案的一样,只需把调用线程改一下。输出可以看到都成功了

在这里插入图片描述

PS:把LocalDate 转换为 Date如下:

/**
* atStartOfDay():
* atZone():
* toInstant():
*/
Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());

总结:以上总结了三种方式解决SimpleDateFormat转换日期在多线程场景情况下存在的异常问题。但他们也有缺点~~ 方法一创建了多个SimpleDateFormat类的实例比较占用内存,效率较低;方法二使用ThreadLocal不会自动释放,如果不手动remove()容易造成内存泄露;个人建议使用方法三DateTimeFormatter处理。

完…

相关文章:

  • 网站建设系统开发/怎么找推广渠道
  • 网站建设邮/seo每日
  • 郑州网站建设公司招聘/免费的推文制作网站
  • 买香港空间上传美女图片做网站/seo前景
  • [ 1500元做网站_验收满意再付款! /网站制作和推广
  • 定制制作网站开发/app制作
  • 【十 三】Netty 私有协议栈开发
  • 腾讯安全发布《2022年DDoS攻击威胁报告》:DDoS威胁4年持续增长
  • 渗透测试— —扫描与爆破账号
  • QEMU零知识学习4 —— QEMU编译
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • 权限管理---尚硅谷
  • 6、Denoising Diffusion Probabilistic Models(扩散模型)
  • Linux操作系统进程状态Linux内核进程状态
  • Servlet 之 Responses
  • 如何写好JS
  • 【1813. 句子相似性 III】
  • dp解最长回文子串