Java总结系列

【Java系列】JDK JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。 阅读详情

SimpleDateFormat的线程安全问题与解决方案

  1. 原因

SimpleDateFormat(下面简称sdf)类内部有一个Calendar对象引用,它用来储存和这个sdf相关的日期信息,例如sdf.parse(dateStr), sdf.format(date) 诸如此类的方法参数传入的日期相关String, Date等等, 都是交友Calendar引用来储存的.这样就会导致一个问题,如果你的sdf是个static的, 那么多个thread 之间就会共享这个sdf, 同时也是共享这个Calendar引用, 并且, 观察 sdf.parse() 方法,你会发现有如下的调用:

复制代码
Date parse() {

calendar.clear(); // 清理calendar

… // 执行一些操作, 设置 calendar 的日期什么的

calendar.getTime(); // 获取calendar的时间

}
复制代码
这里会导致的问题就是, 如果 线程A 调用了 sdf.parse(), 并且进行了 calendar.clear()后还未执行calendar.getTime()的时候,线程B又调用了sdf.parse(), 这时候线程B也执行了sdf.clear()方法, 这样就导致线程A的的calendar数据被清空了(实际上A,B的同时被清空了). 又或者当 A 执行了calendar.clear() 后被挂起, 这时候B 开始调用sdf.parse()并顺利i结束, 这样 A 的 calendar内存储的的date 变成了后来B设置的calendar的date

  1. 问题重现

复制代码
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**

  • @author zhenwei.liu created on 2013 13-8-29 下午5:35

  • @version IdIdId
    */
    public class DateFormatTest extends Thread {
    private static SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd”);

    private String name;
    private String dateStr;
    private boolean sleep;

    public DateFormatTest(String name, String dateStr, boolean sleep) {
    this.name = name;
    this.dateStr = dateStr;
    this.sleep = sleep;
    }

    @Override
    public void run() {

     Date date = null;
    
     if (sleep) {
         try {
             TimeUnit.MILLISECONDS.sleep(2000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
    
     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();
    
     // A 会sleep 2s 后开始执行sdf.parse()
     executor.execute(new DateFormatTest("A", "1991-09-13", true));
     // B 打了断点,会卡在方法中间
     executor.execute(new DateFormatTest("B", "2013-09-13", false));
    
     executor.shutdown();
    

    }
    }
    复制代码
    使用Debug模式执行这段代码,并在sdf.parse()方法里打上断点

复制代码
parse() {

calendar.clear()

// 这里打一个断点

calendar.getTime()

}
复制代码
过程:

  1. 首先A线程跑起来以后会进入sleep

  2. B线程跑起来, 卡在断点处

  3. A线程醒过来, 执行 calendar.clear(), 并将设置sdf.calendar的date为1991-09-13, 此时 A B 的 calendar 都为 1991-09-13

  4. 让断点继续执行, 输出如下

A : date: Fri Sep 13 00:00:00 CDT 1991
B : date: Fri Sep 13 00:00:00 CDT 1991

这并不是我们期待的结果

  1. 解决方案

最简单的解决方案我们可以把static去掉,这样每个新的线程都会有一个自己的sdf实例,从而避免线程安全的问题

然而,使用这种方法,在高并发的情况下会大量的new sdf以及销毁sdf,这样是非常耗费资源的

在并发情况下,网站的请求任务与线程执行情况大概可以理解为如下

例如Tomcat的线程池的最大Thread数为4, 现在需要执行的任务有1000个(理解为有1000个用户点了你的网站的某个功能),

而这1000个任务都会用到我们写的日期函数处理类

A) 假如说日期函数处理类使用的是new SimpleDateFormat的方法,那么这里就会有1000次sdf的创建和销毁

B) Java中提供了一种ThreadLocal的解决方案,它的工作方式是,每个线程只会有一个实例,也就是说我们执行完这1000个任务,总共只会实例化4个sdf.

而且,它并不会有多线程的并发问题,因为,单个线程执行任务肯定是顺序的,例如Thread #1负责执行Task #1-#250, 那么他是顺序而执行Task #1-#250

而Thread #2拥有自己的sdf实例,他也是顺序执行任务 Task #251-#500, 以此类推

下面是一个使用ThreadLocal解决sdf多线程问题的例子

复制代码
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**

  • @author zhenwei.liu created on 2013 13-8-29 下午5:35

  • @version IdIdId
    */
    public class DateUtil {

    /** 锁对象 */
    private static final Object lockObj = new Object();

    /** 存放不同的日期模板格式的sdf的Map */
    private static Map<String, ThreadLocal> sdfMap = new HashMap<String, ThreadLocal>();

    /**

    • 返回一个ThreadLocal的sdf,每个线程只会new一次sdf

    • @param pattern

    • @return
      */
      private static SimpleDateFormat getSdf(final String pattern) {
      ThreadLocal tl = sdfMap.get(pattern);

      // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
      if (tl == null) {
      synchronized (lockObj) {
      tl = sdfMap.get(pattern);
      if (tl == null) {
      // 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
      System.out.println(“put new sdf of pattern " + pattern + " to map”);

               // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
               tl = new ThreadLocal<SimpleDateFormat>() {
      
                   @Override
                   protected SimpleDateFormat initialValue() {
                       System.out.println("thread: " + Thread.currentThread() + " init pattern: " + pattern);
                       return new SimpleDateFormat(pattern);
                   }
               };
               sdfMap.put(pattern, tl);
           }
       }
      

      }

      return tl.get();
      }

    /**

    • 是用ThreadLocal来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
    • @param date
    • @param pattern
    • @return
      */
      public static String format(Date date, String pattern) {
      return getSdf(pattern).format(date);
      }

    public static Date parse(String dateStr, String pattern) throws ParseException {
    return getSdf(pattern).parse(dateStr);
    }

}
复制代码
测试类

复制代码
import java.text.ParseException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**

  • @author zhenyu.nie created on 2013 13-8-26 下午2:13

  • @version 1.0.0
    */
    public class Test {

    public static void main(String[] args) {
    final String patten1 = “yyyy-MM-dd”;
    final String patten2 = “yyyy-MM”;

     Thread t1 = new Thread() {
    
         @Override
         public void run() {
             try {
                 DateUtil.parse("1992-09-13", patten1);
             } catch (ParseException e) {
                 e.printStackTrace();
             }
         }
     };
    
     Thread t2 = new Thread() {
    
         @Override
         public void run() {
             try {
                 DateUtil.parse("2000-09", patten2);
             } catch (ParseException e) {
                 e.printStackTrace();
             }
         }
     };
    
     Thread t3 = new Thread() {
    
         @Override
         public void run() {
             try {
                 DateUtil.parse("1992-09-13", patten1);
             } catch (ParseException e) {
                 e.printStackTrace();
             }
         }
     };
    
     Thread t4 = new Thread() {
    
         @Override
         public void run() {
             try {
                 DateUtil.parse("2000-09", patten2);
             } catch (ParseException e) {
                 e.printStackTrace();
             }
         }
     };
    
     Thread t5 = new Thread() {
    
         @Override
         public void run() {
             try {
                 DateUtil.parse("2000-09-13", patten1);
             } catch (ParseException e) {
                 e.printStackTrace();
             }
         }
     };
    
     Thread t6 = new Thread() {
    
         @Override
         public void run() {
             try {
                 DateUtil.parse("2000-09", patten2);
             } catch (ParseException e) {
                 e.printStackTrace();
             }
         }
     };
    
     System.out.println("单线程执行: ");
     ExecutorService exec = Executors.newFixedThreadPool(1);
     exec.execute(t1);
     exec.execute(t2);
     exec.execute(t3);
     exec.execute(t4);
     exec.execute(t5);
     exec.execute(t6);
     exec.shutdown();
    
     sleep(1000);
    
     System.out.println("双线程执行: ");
     ExecutorService exec2 = Executors.newFixedThreadPool(2);
     exec2.execute(t1);
     exec2.execute(t2);
     exec2.execute(t3);
     exec2.execute(t4);
     exec2.execute(t5);
     exec2.execute(t6);
     exec2.shutdown();
    

    }

    private static void sleep(long millSec) {
    try {
    TimeUnit.MILLISECONDS.sleep(millSec);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    复制代码
    输出

单线程执行:
put new sdf of pattern yyyy-MM-dd to map
thread: Thread[pool-1-thread-1,5,main] init pattern: yyyy-MM-dd
put new sdf of pattern yyyy-MM to map
thread: Thread[pool-1-thread-1,5,main] init pattern: yyyy-MM
双线程执行:
thread: Thread[pool-2-thread-1,5,main] init pattern: yyyy-MM-dd
thread: Thread[pool-2-thread-2,5,main] init pattern: yyyy-MM
thread: Thread[pool-2-thread-1,5,main] init pattern: yyyy-MM
thread: Thread[pool-2-thread-2,5,main] init pattern: yyyy-MM-dd

从输出我们可以看出:

  1. 1个线程执行这6个任务的时候,这个线程首次使用过的时候会new一个新的sdf,并且以后都一直用这个sdf,而不是每次处理任务都新建一个新的sdf

  2. 2个线程执行6个任务的时候也是同理,但是2个线程的sdf是分开的,每个线程都有自己的"yyyy-MM-dd", "yyyy-MM"的sdf,所以他们不会有线程安全安全问题

试想,如果使用的是new的实现方法,那么不管是用1个线程去执行,还是用2个线程去执行这6个任务,都需要new 6个sdf

Java入门第99课——使用parse方法时将字符串解析为日期 码字不易,请大家关注公众号,转发,点赞,只需动动手指,不要钱,谢谢了老铁! 问题 在上一案例的基础上,将字符串表示的日期"2013-12-25"转换为Date类型表示的日期。 方案 1)实例化SimpleDateFormat类的对象,该类可以使用户选择自定义的日期-时间格式的模式,代码如下所示: SimpleDateFormatsdf=newSimpleDate... 阅读详情

相关推荐

Inside NAND Flash Memories.中文版. Chatper 2 [2.1-2.2.1]

2.1 Introduction 半导体存储器分类: RAM:Random Access Memories--掉电丢失 ROM:Read Only Memories--只读,出厂时写好;掉电不丢失;不可更改。 NVM:Non-Volatile Memories--电可擦除/写入,并且掉电不丢失。第一类产品为EPROM memory (Erasable Programmable Read...

xuqikang的专栏 3606

如何将:sdf.parse(dateStr)方法返回的Date类型时间数据: Fri May 10 00:00:00 CST 2019 转为:yyyy-MM-dd格式的Date类型数据

背景: 首先我的需求是:必须要一个Date类型且格式为yyyy-mm-dd的时间数据。下面是问题产生及解决办法详细: 数据库保存了一个date类型的时间数据(格式为 yyyy-MM-dd,比如:2019-05-10) java后台需要查出这个时间数据,丢给一个bean对象缓存(以Java.Util.Date类型保存),然后丢给前台。 但是后台从数据库取出时间数据丢给bean保存下来的数据...

aigoV的博客 7881

DELL Precision T7920加装显卡遇到的坑

文章目录DELL Precision T7920加装显卡服务器原有配置介绍选购过程确认型号确认PCIE x16插槽确认电源功率和电源线安装过程电源模组线不够多单装一个3090显卡尺寸问题加装上1080Ti非常重要!官方文档 DELL Precision T7920加装显卡 实验室由于显卡计算能力不够,以及显卡显存不够,请老师花了大价钱购入了两张GeForce 3090显卡。老师赛高!记录一下这次加装显卡过程中学到的一些知识。 服务器原有配置介绍 服务器原有一张1080Ti显卡,一个16核Xeon CPU。

weixin_45520982的博客 1万+

SimpleDateFormat 日期格式化,以及sdf.parse(“2022-02-30“)顺序加问题

时间日期标识符: 要注意其中的大小写哦,不然会格式化后的日期会很乱的!!!同时没个字母可以写多个,如:yyyyyyyyyyy。根据你的数大小,根据写的字母的长度,在前面补零。比如:你要把2022 - 05 - 20 13:14:00这个时间格式化成2022 年 05 月 20 日 13 时 14 分 00 秒一不小心把格式化日期中时间标识符中的大小写搞错了。 这个MM这里写的是月份的表示。而分钟的表示则是mm。所以要注意下面是把日期格式化成最全的例子。 输出的结果: 输出结果: 标识符表格:simpl

qq_43842093的博客 2766

miniDP与DP管脚定义差异详解及设计避坑指南

本文详细解析了Mini DisplayPort与标准DisplayPort接口在管脚定义上的核心差异,通过对比表格直观展示了两者20针脚的不同排列方式,特别是高速差分信号线的分布。文章结合真实设计案例,为硬件工程师提供了从原理图设计、PCB布局到信号完整性考量及故障排查的完整避坑指南,强调绝不能将两者视为简单的物理缩小版。

weixin_29248653的博客 362

别再到处找密钥了!手把手教你搞定Halcon 24.11/25.05的试用授权(附每月更新提醒)

本文详细介绍了Halcon 24.11/25.05试用授权的获取与配置方法,包括解密新版授权机制、快速配置指南、每月更新规律及多版本共存管理技巧。帮助开发者避免常见授权问题,提升工作效率,特别适合机器视觉领域的初学者和专业人士。

weixin_29238147的博客 1142

时间相关类

java.util.Date`类 表示特定的瞬间,精确到毫秒。继续查阅Date类的描述,发现Date拥有多个构造函数,只是部分已经过时,我们重点看以下两个构造函数:从运行程序的此时此刻到时间原点经历的毫秒值,转换成Date对象,分配Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。:将指定参数的毫秒值date,转换成Date对象,分配Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即1970年1月1日00:00:00 GMT)以来的指定毫秒数。

CVPR收割机的博客 1156

Kettle下载与安装教程【保姆版】

Kettle简介 Kettle最早是一个开源的ETL工具,全称为KDE Extraction, Transportation, Transformation and Loading Environment。后来Kettle重命名为Pentaho Data Integration 。 它由Java开发,支持跨平台运行,其特性包括:支持100%无编码、拖拽方式开发ETL数据管道;可对接包括传统数据库、文件、大数据平台、接口、流数据等数据源;支持ETL数据管道加入机器学习算法。 主要功能 Pentaho D

weixin_43407520的博客 12万+

java sdf.parse_用DateFormat的parse方法验证字符串是否是日期的问题

因为最近比较闲 有点不思进取 也没什么问题要写了今天不错,发现一个,恩,感觉不错做了一个上传Excel并解析数据然后入库的功能,里面涉及到是否是日期格式的判断之前是这么写的(深谙这么写是不好的,但以为它是可靠的)private boolean checkDate(String content) {SimpleDateFormat sdf = new SimpleDateFormat("yyyy-M...

weixin_39602569的博客 1451

redhawk:使用rh_setup.pl产生gsr模板

必须加的option工具会在rh_setup.pl给出提示,按照要求填写即可,另外工具会把所有填写的输入文件和信息放到rh_setup.init文件中,拷贝这个文件可在任何平级运行目录下,直接rh_setup.pl产生gsr以及run sta/dyn/em的脚本。rhsetup.pl -top top_cell_name -vdd_nets VDD 1.2 VDD2 1.2 -vss_nets VSS -frequency 100e6 (100m是默认值)更多学习内容请关注「拾陆楼」知识星球。

m0_61544122的博客 1051

【HAL库】STM32CubeMX开发----STM32F407----ETH+LAN8720A+LWIP----ping通

本次实验以芯片为MCU,使用25MHz外部时钟源。以太网PHY层芯片为LAN8720A,由MCU引脚PA8提供时钟。

MQ0522的博客 4843

Java基础系列合集

​Java基础系列整理到这里,也就告一段落了。接下来计划先整理HTML/CSS基础系列(目前在计划中的有vue基础系列,Java进阶系列,spring系列等。如果之后有时间,有能力,就会慢慢安排上的。)。希望整理的内容也能够对大家有一定的作用,毕竟做的事情有价值才有动力去做。 Java基础系列–01–走进Java Java基础系列–02–常量、变量、数据类型 Java基础系列–03–数据类型转换、运算符 Java基础系列–04–流程控制、随机数 Java基础系列–05–数组 Java基础系列–06–方法 J

ITarmi的分享 206

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

[转载]Java集合系列大全总结

Java 集合系列目录(Category) 22:06:49 2019-02-27 转载于:https://www.cnblogs.com/chiweiming/p/10447366.html

weixin_33962621的博客 121

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Java 集合总结(Collection系列与Map系列)

Java 集合总结(Collection系列与Map系列)

hutongling的博客 453

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

聊一聊Java中的系列化

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34067102的博客 305

Java原理示例源码详解系列文档总结

Java原理示例源码详解系列文档总结

wang2leee的博客 715

Java泛型学习系列【转】

Java泛型学习系列

kermit的博客 418

《Java千问》系列短文写作计划

大家好,我在优快云开博客啦! 我本人从事软件开发和教学多年,在我从事教学过程中,回答过很多学生的问题,但非常遗憾,我只是当场回答了这些问题,但事后并没有把这些问题的解答写成博客。我打算弥补一下这个缺憾,因此产生了写文章解答问题的想法。 经过几天酝酿,我打算把这些文章写成系列短文,名字就叫《Java千问系列》。取这个名字并不是说真的有一千个问题,“千”只是表示多的意思。只要我有把握回答的,都发表出来。关于《Java千问系列》的文章,在这里我想做几点说明

穆哥学堂--Java学习园地 478

Java Set系列集合

Java Set系列集合

m0_60731649的博客 547

JAVA入门——Set系列集合

有序可排序Set接口中的方法与Collection中的API基本一致,直接用即可。

2301_80216298的博客 1105

JAVA爬虫系列教程

java爬虫系列第五讲-如何使用代理防止爬虫被屏蔽? JAVA爬虫 快速入门

凌波漫步 448

java系列教程_Java系列教程

#Java系列教程本系列教程分为两个部分:基础篇和提高篇。基础篇位于guide目录,文件和目录列表如下:code:示例代码imgs:本书用到的图片,大多数图片包含dia源文件*.lyx:本教材各章节lyx源代码,其中的java-guide-on-c.lyx为主控文件。preamble.tex:Lyx的导言设置,在这里设置纸张大小、代码列表的样式等。这些也 可以在Lyx的图形用户界面设置,但是通过导...

weixin_33542801的博客 230

Java集合系列详解

一,集合简介 Java中的集合可以分为四种体系: List:代表有序、重复的集合 Set:代表无序、不可重复的集合 Map:代表具有映射关系的集合 Queue:体系集合,代表一种队列集合实现 二,Java集合类的关系 Java中的集合类主要有两个接口派生:Collection和Map Collection类图如下: Map类图如下: 三,Collection接口 1,Set体系 Set是一种...

wang_yong_hui_1234的博客 5989

Java反射学习系列【转】

Java反射学习系列

kermit的博客 269

Java 必读书籍系列推荐

Java 进阶书籍推荐

猫猫聚会ing ~ 的 博客 127
上一篇: 数据库表结构设计的一些总结
下一篇: Kafka概述
张焱飞
博客等级 码龄11年 8粉丝 12原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值