Java中SimpleDateFormat的线程安全性问题

这篇具有很好参考价值的文章主要介绍了Java中SimpleDateFormat的线程安全性问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但不同的方法获取到的时间格式不尽相同,这时就需要一种格式化工具,把时间显示成我们需要的格式,最常用的方法就是使用SImpleDateFormat类。这是一个看上去功能比较简单的类,但使用不当,也有可能导致很大的问题.
在《阿里巴巴Java开发手册》中,有明确规定
【强制】SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类

1.SimpleDateFormat的用法

public class MyDate {
    public static void main(String[] args) throws ParseException {
        Date data = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String dataStr = sdf.format(data);
        System.out.println(dataStr);

        // Strubg 转Date
        System.out.println(sdf.parse(dataStr));
    }
}

Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言

2.SimpleDateFormat线程的安全性

由于SimpledateFormat比较常用,而且在一般情况下,一个应用中时间的显示模式都是一样的,所以很多人愿意使用如下方式定义SimpleDateFormat

public class MyDate {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws ParseException {
        String format = sdf.format(Calendar.getInstance().getTime());
        System.out.println("format = " + format);
    }
}

Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言
这种定义方式存在很大的安全隐患

2.1 问题重现

当我们使用线程池来输出时间

public class MyDate {

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private static ThreadFactory nameThreadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("demo-pool-%d");
            return t;
        }
    };

    private static ExecutorService pool = new ThreadPoolExecutor(5,200,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024), nameThreadFactory, new ThreadPoolExecutor.AbortPolicy());


    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        // 定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());

        for (int i = 0; i < 100; i++) {

            // 获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(() -> {
                // 时间增加
                calendar.add(Calendar.DATE, finalI);
                // 通过simpleDateFormat把时间转换成字符串
                String dateString = sdf.format(calendar.getTime());
                // 把字符串放入set
                dates.add(dateString);

                // countDownLatch
                countDownLatch.countDown();
            });

        }

        // 阻塞,直到countDown数量为0
        countDownLatch.await();
        // 输出去重后的时间个数
        System.out.println(dates.size());
        // 线程池关闭
        pool.shutdown();
    }
}

Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言
代码逻辑比较简单,循环执行一百次,每次循环执行时都在当前时间的基础上增加一个天数(这个天数随着循环次数而变化),然后把所有日期放入一个线程安全的、带有去重功能的set中,最后输出Set元素的格式,正常情况下,以上代码的输出结果应该是100.但实际执行的结果是一个小于100的数字,这是因为SImpleDateFormat作为一个非线程安全的类,被当作共享变量在多个线程中使用,这就出现了线程安全问题.
这个问题在JDK文档中已经明确表明了SimpleDateFormat不应该用在多线程场景中
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

2.2 线程不安全的原因

关键在于SimpleDateFormat#format()
Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言
DateFormat中的calendar变量是一个成员变量,它每次在格式化时都用会该变量保存当前时间,由于我们在使用时将它声明为了static类型,SimpleDateFormat中的calendar也就可以被多个线程访问,一个共享变量在多线程环境下进行修改必然会引起不安全。同理SimpleDateFormat的parse方法也有同样的问题
Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言

2.3 解决方式

  • 使用局部变量
    public static void main(String[] args) throws InterruptedException {
        // 定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());

        for (int i = 0; i < 100; i++) {

            // 获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(() -> {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                // 时间增加
                calendar.add(Calendar.DATE, finalI);
                // 通过simpleDateFormat把时间转换成字符串
                String dateString = sdf.format(calendar.getTime());
                // 把字符串放入set
                dates.add(dateString);

                // countDownLatch
                countDownLatch.countDown();
            });

        }

        // 阻塞,直到countDown数量为0
        countDownLatch.await();
        // 输出去重后的时间个数
        System.out.println(dates.size());
        // 线程池关闭
        pool.shutdown();
    }

Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言

  • 加同步锁
    public static void main(String[] args) throws InterruptedException {
        // 定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());

        for (int i = 0; i < 100; i++) {

            // 获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(() -> {
                synchronized (sdf) {
                    // 时间增加
                    calendar.add(Calendar.DATE, finalI);
                    // 通过simpleDateFormat把时间转换成字符串
                    String dateString = sdf.format(calendar.getTime());
                    // 把字符串放入set
                    dates.add(dateString);

                    // countDownLatch
                    countDownLatch.countDown();
                }

            });

        }

        // 阻塞,直到countDown数量为0
        countDownLatch.await();
        // 输出去重后的时间个数
        System.out.println(dates.size());
        // 线程池关闭
        pool.shutdown();
    }

Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言

  • 使用ThreadLocal
package other;

import org.apache.commons.collections.CollectionUtils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author xieh
 * @date 2024/01/24 22:25
 */
public class MyDate {

    private static ThreadLocal<SimpleDateFormat> sdfThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    private static ThreadFactory nameThreadFactory = new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName("demo-pool-%d");
            return t;
        }
    };

    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<>(1024), nameThreadFactory, new ThreadPoolExecutor.AbortPolicy());


    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        // 定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());

        for (int i = 0; i < 100; i++) {

            // 获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(() -> {
                SimpleDateFormat sdf = sdfThreadLocal.get();
                // 时间增加
                calendar.add(Calendar.DATE, finalI);
                // 通过simpleDateFormat把时间转换成字符串
                String dateString = sdf.format(calendar.getTime());
                // 把字符串放入set
                dates.add(dateString);

                // countDownLatch
                countDownLatch.countDown();

            });

        }

        // 阻塞,直到countDown数量为0
        countDownLatch.await();
        // 输出去重后的时间个数
        System.out.println(dates.size());
        // 线程池关闭
        pool.shutdown();
    }
}

  • 使用DateTimeFormatter
    需要JDK8版本的支持,这是一个线程安全的格式化工具类
    public static void main(String[] args) {
        // 解析日期
        String dateStr = "2024年01月24日";
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
        LocalDate parse = LocalDate.parse(dateStr, dateTimeFormatter);
        System.out.println("parse = " + parse);

        // 将日期转换为字符串
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm a");
        String format = now.format(formatter);
        System.out.println("format = " + format);


    }

Java中SimpleDateFormat的线程安全性问题,Java,java,开发语言

2.4 总结

解决线程不安全的主要手段有使用局部变量、synchronized加锁、ThreadLocal为每一个线程单独创建一个对象等文章来源地址https://www.toymoban.com/news/detail-823754.html

到了这里,关于Java中SimpleDateFormat的线程安全性问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java应用服务系统安全性,签名和验签浅析

    随着互联网的普及,分布式服务部署越来越流行,服务之间通信的安全性也是越来越值得关注。这里,笔者把应用与服务之间通信时,进行的的安全性相关, 加签 与 验签 ,进行了一个简单的记录。 网关服务接口,暴漏在公网,被非法调用? 增加了token安全验证,被抓包等

    2024年02月13日
    浏览(34)
  • java生成一个符合密码学和安全性的随机秘钥

    有时 我们在生成token 或者完成某种加密形式时会需要一个秘钥 但是 有些时候 项目开发并没有规定用什么秘钥 但是 秘钥都是要有一定格式规范的 我们可以通过以下代码生成一个随机秘钥 重点是 这种一定会符合密码学和安全规范

    2024年02月07日
    浏览(33)
  • Java面试题:SimpleDateFormat是线程安全的吗?使用时应该注意什么?

    在Java开发中,我们经常需要获取和处理时间,这需要使用到各种不同的方法。其中,使用SimpleDateFormat类来格式化时间是一种常见的方法。虽然这个类看上去功能比较简单,但是如果使用不当,也可能会引发一些问题。 首先我们要明确一点, SimpleDateFormat不是线程安全的 。

    2024年04月26日
    浏览(30)
  • 前端面试:【前端安全】安全性问题与防范措施

    嗨,亲爱的前端开发者!在构建Web应用程序时,确保安全性是至关重要的。本文将深入讨论前端开发中的安全性问题,并提供一些防范措施,以确保你的应用程序和用户数据的安全性。 前端安全性问题: 跨站脚本攻击(XSS): XSS攻击发生在恶意用户将恶意脚本注入到网页中

    2024年02月11日
    浏览(36)
  • SimpleDateFormat 线程安全问题修复方案

    在日常的开发过程中,我们不可避免地会使用到 JDK8 之前的 Date 类,在格式化日期或解析日期时就需要用到 SimpleDateFormat 类,但由于该类并不是线程安全的,所以我们常发现对该类的不恰当使用会导致日期解析异常,从而影响线上服务可用率。 以下是对 SimpleDateFormat 类不恰当

    2024年02月12日
    浏览(32)
  • 商用密码应用安全性评估(密评)六大基础问题解答

    2021年3月9日,国家市场监管总局、国家标准化管理委员会发布公告,正式发布国家标准GB/T39786-2021《信息安全技术 信息系统密码应用基本要求》,该标准将于2021年10月1日起正式实施。这是贯彻落实我国《密码法》,指导密评工作的一项基础性标准,对于规范和引导信息系统合规、正

    2024年02月16日
    浏览(36)
  • 搞懂 API,跨域资源贡献 (CORS )和安全性问题

    在 Web 应用开发中,API 是应用程序和其他系统之间进行数据交互的主要方式。 跨域资源共享(CORS)是一种常见的处理跨域请求的技术,但同时也带来了一些安全性问题。我将分享 CORS 技术及其安全性问题。 CORS 是指在浏览器端实现的机制,允许 Web 应用程序或 API 向不同的域

    2024年02月03日
    浏览(38)
  • 边缘计算的挑战和机遇:数据的安全性和隐私性问题

    随着边缘计算技术的迅猛发展,数据的安全性和隐私性问题变得愈发重要。在分布式计算环境中,如何确保边缘计算中的数据安全性和隐私性成为亟待解决的问题。本文将深入讨论在边缘计算中设计有效的安全机制和隐私保护算法,以防止数据泄露和篡改。 1.1 分布式环境下

    2024年01月23日
    浏览(32)
  • 物联网中基于信任的安全性调查研究:挑战与问题

    A survey study on trust-based security in Internet of Things: Challenges and issues 随着物联网在社会中的应用越来越多,它面临的安全挑战也越来越严峻。物联网中收集和共享的数据在物联网的重要性中发挥着重要作用。从数据角度进行观察可能对理解物联网安全有很大帮助。尽管已经有许多

    2024年02月21日
    浏览(34)
  • 【Docker】云原生利用Docker确保环境安全、部署的安全性、安全问题的主要表现和新兴技术产生

    前言 Docker 是一个 开源的应用容器引擎 ,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux或Windows 操作系统的机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。   Docker十分火热,很多人表示很少见如

    2024年02月11日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包