SimpleDateFormat并发隐患及其解决

此文已由作者姚太行授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

SimpleDateFormat被大量使用于处理时间格式化过程,由于该类在创建时会指定一个pattern用于标明固定的时间格式,所以在使用中,一般会创建一个作用域较大(static修饰或某类的私有属性)的对象用于重复使用。由于时间转换过程遇到的多线程并发的使用场景并不多见,所以很难发现在该类的隐患,事实上,该类并非是线程安全的,在多线程使用format()和parse()方法时可能会遇到问题。

分析

在SimpleDateFormat及其父类DateFormat的源文件里,有这样一段说明:

* 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.

JDK文档中已经明确指出,这两个类在进行时间格式化的过程中都是非线程安全的。也就是说,使用同一个SimpleDateFormat实例,开若干线程做日期转换操作,得到的结果可能并不准确。

parse

parse()测试,参考了其他人对此做的实验,我使用的测试代码(jdk1.8)如下:

import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class DateFormatTest extends Thread {    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");    private String name;    private String dateStr;    public DateFormatTest(String name, String dateStr) {        this.name = name;        this.dateStr = dateStr;
    }    @Override
    public void run() {

        Date date = null;        try {
            date = sdf.parse(dateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        System.out.println(name + " : date: " + date);
    }    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newCachedThreadPool();

        executor.execute(new DateFormatTest("Test_A", "2000-04-28"));
        executor.execute(new DateFormatTest("Test_B", "2017-04-28"));

        executor.shutdown();
    }
}

这段测试代码参考了网上一段用例,与之不同的是,原用例中在两个线程操作中间做了线程等待Sleep,而为了看到效果,修改后的测试用例把线程等待的部分去掉。虽然每次运行的结果都会不太一样,但经常会抛出的异常:

Exception in thread "pool-1-thread-1" java.lang.NumberFormatException: For input string: ""
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Long.parseLong(Long.java:601)
    at java.lang.Long.parseLong(Long.java:631)
    at java.text.DigitList.getLong(DigitList.java:195)
    at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
    at java.text.DateFormat.parse(DateFormat.java:364)
    at DateFormatTest.run(DateFormatTest.java:24)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Test_B : date: Mon Apr 28 00:00:00 CST 2200

需要明确的是,待转换的字符串作为非静态私有变量是每个对象持有的,只有sdf本身是公用的,不难发现即便是成功输出了,但是数值也未必会是正确的,parse()方法不安全。

format

SimpleDateFormat的format()方法源码如下:

private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {        // Convert input date to time field list
        calendar.setTime(date);
        ...

需要注意的是calendar的操作并非是线程安全的,很显然在并发情景下,format的使用并不安全,测试过程与对parse过程的测试相似,不再赘述。

解决

既然SimpleDateFormat本身并不安全,那么解决的方式无非两种:优化使用过程或者找替代品。

1.临时创建

不使用Static,每次使用时,创建新实例。

存在的问题:

SimpleDateFormat中使用了Calendar对象,由于该对象相当重,在高并发的情况下会大量的new SimpleDateFormat以及销毁SimpleDateFormat,极其耗费资源。

2.synchronized

以synchronized同步SimpleDateFormat对象。

存在的问题:

高并发时,使用该对象会出现阻塞,当前使用者使用时,其他使用者等待,尽管结果是对的,但是并发成了排队,实际上并没有解决问题,还会对性能以及效率造成影响。

3.ThreadLocal

使用ThreadLocal,令每个线程创建一个当前线程的SimpleDateFormat的实例对象。

存在的问题:

使用ThreadLocal时,如果执行原子任务的过程是每一个线程执行一个任务,那么这样的声明基本和每次使用前创建实例对象是没区别的;如果使用的是多线程加任务队列,举个例子,tomcat有m个处理线程,外部有n个待处理任务请求,那么当执行n个任务时,其实只会创建m个SimpleDateFormat实例,对于单一的处理线程,执行任务是有序的,所以对于当前线程而言,不存在并发。

4.Apache的 DateFormatUtils 与 FastDateFormat

使用org.apache.commons.lang.time.FastDateFormat 与 org.apache.commons.lang.time.DateFormatUtils。

存在的问题:

apache保证是线程安全的,并且更高效。但是DateFormatUtils与FastDateFormat这两个类中只有format()方法,所有的format方法只接受long,Date,Calendar类型的输入,转换成时间串,目前不存在parse()方法,可由时间字符串转换为时间对象。

5.Joda-Time

使用Joda-Time类库。

存在的问题:

没有问题~

简介:

Joda-Time — 面向 Java 应用程序的日期/时间库的替代选择,Joda-Time 令时间和日期值变得易于管理、操作和理解。事实上,易于使用是 Joda 的主要设计目标。其他目标包括可扩展性、完整的特性集以及对多种日历系统的支持。并且 Joda 与 JDK 是百分之百可互操作的,因此您无需替换所有 Java 代码,只需要替换执行日期/时间计算的那部分代码。

资料:

Joda-Time 简介(中文)https://www.ibm.com/developerworks/cn/java/j-jodatime.html
Joda-Time 文档(英文)http://joda-time.sourceforge.net/



“欲要看究竟,处处细留心。”  —宋帆

免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 网易云数据库架构设计实践
【推荐】 jq一个强悍的json格式化查看工具

原文地址:https://www.cnblogs.com/163yun/p/9876687.html

时间: 2024-08-10 07:30:50

SimpleDateFormat并发隐患及其解决的相关文章

Java高并发,如何解决,什么方式解决

对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研究一下常见的并发和同步吧. 为了更好的理解并发和同步,我们需要先明白两个重要的概念:同步和异步    1.同步和异步的区别和联系 所谓同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到 返回的值或消息后才往下执行其它的命令. 异步,执行完函数或方法

java高并发,如何解决,什么方式解决 (转)

之前我将高并发的解决方法误认为是线程或者是队列可以解决,因为高并发的时候是有很多用户在访问,导致出现系统数据不正确.丢失数据现象,所以想到的是用队列解决,其实队列解决的方式也可以处理,比如我们在竞拍商品.转发评论微博或者是秒杀商品等,同一时间访问量特别大,队列在此起到特别的作用,将所有请求放入队列,以毫秒计时单位,有序的进行,从而不会出现数据丢失系统数据不正确的情况. 今天我经过查资料,高并发的解决方法有俩种,一种是使用缓存.另一种是使用生成静态页面:还有就是从最基础的地方优化我们写代码减少不必

ConcurrentModificationException 集合并发修改异常 解决

import java.util.ArrayList; import java.util.List; import java.util.ListIterator; /** * 问题? * 有一个集合,如下,请问,我想判断里面有没有"world"这个元素,如果有,我就添加一个"javaee"元素,请写代码实现. * * ConcurrentModificationException: 但方法监测到对象的并发修改,但不允许这种修改时,抛出此异常. * 产生的原因: *

高并发网站之解决策略

系统在正式上线后必将会面对大量用户访问,面对各种层级的高并发请求,因此我们会采用高性能的服务器.高性能的数据库.高效率的编程语言.高性能的Web容器等.但是这几个方面,还无法从根本解决大型网站面临的高负载和高并发问题.因此我们必须对此做出相应的策略和技术解决方案. 1. 负载均衡 负载均衡将是大型网站解决高负荷访问和大量并发请求采用的终极解决办法. (1)单个重负载的运算分担到多台节点设备上做并行处理,每个节点设备处理结束后,将结果汇总,返回给用户,系统处理能力得到大幅度提高. (2)大量的并发

java 生产者消费者问题 并发问题的解决(转)

引言 生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况: 生产者消费者图 存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品.互相等待,从而发生死锁. 生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品.解决生产者/

高并发思考和解决办法

系统高并发操作会出现系统访问性能问题,死锁,数据不同步等一系列问题.用电商系统来说,高并发下,会出现访问的订单状态不一致的情况.那么可以考虑对此问题做集群处理. 客户端2亿访问量就是高并发业务场景,会出现相应的问题. 现在考虑组成集群 经过负载后 压力平摊到多个节点,分担单实例的压力(多实例+负载),如图: 应用组成了集群,但是连接的还是一个存储,这个时候,所有的实例如果在储存上还是一个库,瓶颈就在存储这一边了,在mysql下,可以考虑住数据库集群,主从配置,读写分离. 这样就储存的压力也分开了

直播中的高并发问题如何解决?

对于爱好观看直播的用户来说,能够如丝般顺滑地浏览视频是一大极致享受.但实际情况是,当某时段大量用户数据涌入(如观看人数上升,弹幕消息爆发等),若并发结构没有优化好,我们很难不遇到画面卡顿的情况.所以今天拓幻科技来分享一下,在直播系统源码开发过程中,如何正确处理高并发带来的这些卡顿问题呢?一.防盗链处理如果是网页直播间,当前站点没有做防盗链的话,就很容易遭受恶意请求.而过多的恶意请求,会对本身流量就比较大的直播间造成很大负担.比如说有A.B两个直播网站,A站享用了B站的资源,页面嵌入了B站的图片.

关于各地区并发高的解决思路

各位同仁,目前各地区云平台的因非冠病毒引发的学生大量使用,对云平台的并发能力要求极高.单台服务器应对教师用户是完全可以通过优化解决的,但如果再加上十几倍的学生用户,就不是一台服务器可以处理的事情了.经分析,我个人认为主要的矛盾点在于MYSQL数据库的压力太高,究其原因有两个:(1)研发人员没有很好的通过缓存对数据库进行保护,造成大量查询穿透.(2)数据库到目前为止,没有横向扩展的能力. 第一个原因历史很久,短时间内无法根本解决,目前只能通过技术方案+堆硬件服务器(注意:最好是多台物理服务器,如果

项目中遇到并发问题和解决办法

最近在做一朋友帮砍价的活动.建立在微信公众号端的一个电商平台,然后我负责砍价模块. 由于这个模块高并发的几率比较大,所有有些逻辑模块就要采取一些缓存技术和排它锁的用户, 比如:由于项目需求是可以多个人同时砍价,我们又有砍到最低价格的限制,所以不进行处理的话很有可能就会超出我们所限制的价格!所以当用户砍价砍到最低价的时候就需要用到排它锁了 ,直接上代码: //说明砍到最低价 $order_price = $res['bg_order_price']-$res['bg_floorprice']; $