项目记事【多线程】:关于 SimpledDateFormat 的多线程问题
背景:
最近项目引入了 SonarLink,解决代码规范的问题,在检查历史代码的时候,发现了一个问题。
先看代码:
public class DateUtil {
private static final String DATE_FORMAT_1 = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_FORMAT_2 = "yyyy-MM-dd";
private static SimpleDateFormat sdf1 = new SimpleDateFormat(DATE_FORMAT_1);
private static SimpleDateFormat sdf2 = new SimpleDateFormat(DATE_FORMAT_2);
private DateUtil() {
}
public static String formatDate1(Date date) throws ParseException {
return sdf1.format(date);
}
public static String formatDate2(Date date) throws ParseException {
return sdf2.format(date);
}
public static Date parseDate1(String dateStr) throws ParseException {
return sdf1.parse(dateStr);
}
public static Date parseDate2(String dateStr) throws ParseException {
return sdf2.parse(dateStr);
}
}
DateUtil
问题出在什么地方?就出在一个共享的变量 SimpledDateFormat,本身是一个线程不安全的类(由于内部实现使用了 Calendar),导致在多线程情况下可能出错。
多线程检测:
public class DateFormatTest {
public static class TestSimpleDateFormatThreadSafe extends Thread {
@Override
public void run() {
while (true) {
try {
this.join(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
try {
System.out.println(this.getName() + ":" + DateUtil.parseDate1("2013-05-24 06:02:20"));
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new TestSimpleDateFormatThreadSafe().start();
}
}
}
DateFormatTest
执行结果如下图(多次执行,出现的结果可能不同):

解决方案:
这种问题,不仅仅会出现在 SimpleDateFormat 中,只能说 SimpleDateFormat 比较常见,具有代表性。
只要是将一个线程不安全类产生的实例作为共享变量,都有可能出现多线程的问题。
出现这类问题,应该从以下3个角度考虑解决方案:
- 规避将一个线程不安全的对象,作为共享变量的情况。
- 从多线程的角度考虑,解决线程安全问题。
- 不使用线程不安全的对象,找一个拥有相同功能的其他类,作为替代方案。
第一类解决方案:
不要将线程不安全的对象,作为共享变量,在方法内部调用的时候再初始化这个对象。
代码如下:
public class DateUtil1 {
private static final String DATE_FORMAT_1 = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_FORMAT_2 = "yyyy-MM-dd";
private DateUtil1() {
}
public static String formatDate1(Date date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_1);
return sdf.format(date);
}
public static String formatDate2(Date date) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_2);
return sdf.format(date);
}
public static Date parseDate1(String dateStr) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_1);
return sdf.parse(dateStr);
}
public static Date parseDate2(String dateStr) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT_2);
return sdf.parse(dateStr);
}
}
DateUtil1
很显然,大多数情况下,都不会采用这个方案,频繁地创建-销毁对象,对于内存的影响非常大。
第二类解决方案:
从多线程的角度,就是说在使用这个线程不安全类的时候,加以控制,从以下两个角度入手:
- 时间换空间:同步锁 synchronized。
- 空间换时间:独立线程 ThreadLocal。
synchronized
使用 synchronized 的思路很简单,在使用线程不安全变量之前,先将这个变量用 synchronized 关键字锁住。
每一次其他线程使用这个变量时,会等待上一个线程释放这个锁之后再执行。
很明显,在高并发的情况下,这种方案对于时间的消耗很大。
代码如下:
public final class DateUtil2 {
private static final String DATE_FORMAT_1 = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_FORMAT_2 = "yyyy-MM-dd";
private static final SimpleDateFormat SDF_1 = new SimpleDateFormat(DATE_FORMAT_1);
private static final SimpleDateFormat SDF_2 = new SimpleDateFormat(DATE_FORMAT_2);
private DateUtil2() {
}
public static String formatDate1(Date date) throws ParseException {
synchronized (SDF_1) {
return SDF_1.format(date);
}
}
public static String formatDate2(Date date) throws ParseException {
synchronized (SDF_2) {
return SDF_2.format(date);
}
}
public static Date parseDate1(String dateStr) throws ParseException {
synchronized (SDF_1) {
return SDF_1.parse(dateStr);
}
}
public static Date parseDate2(String dateStr) throws ParseException {
synchronized (SDF_2) {
return SDF_2.parse(dateStr);
}
}
}
DateUtil2
ThreadLocal
ThreadLocal,可以简单地这么理解,ThreadLocal 为每一个使用这个线程的变量创建一个独立的副本。
每一个线程都在自己内部改变这个变量,自然不会出现线程安全问题。
明显,这种方案对于内存空间,消耗极大。
代码如下:
public final class DateUtil3 {
private static final String DATE_FORMAT_1 = "yyyy-MM-dd HH:mm:ss";
private static final String DATE_FORMAT_2 = "yyyy-MM-dd";
private static Map<String, ThreadLocal<SimpleDateFormat>> threadLocalMap;
private static List<String> dateFormatStringList;
private DateUtil3() {
}
static {
threadLocalMap = new HashMap<>();
dateFormatStringList = new ArrayList<>();
dateFormatStringList.add(DATE_FORMAT_1);
dateFormatStringList.add(DATE_FORMAT_2);
for (final String s : dateFormatStringList) {
SimpleDateFormat sdf = new SimpleDateFormat(s);
ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat(s);
}
};
threadLocal.set(sdf);
threadLocalMap.put(s, threadLocal);
}
}
public static String formatDate1(Date date) throws ParseException {
SimpleDateFormat sdf = threadLocalMap.get(DATE_FORMAT_1).get();
return sdf.format(date);
}
public static String formatDate2(Date date) throws ParseException {
SimpleDateFormat sdf = threadLocalMap.get(DATE_FORMAT_2).get();
return sdf.format(date);
}
public static Date parseDate1(String dateStr) throws ParseException {
SimpleDateFormat sdf = threadLocalMap.get(DATE_FORMAT_1).get();
return sdf.parse(dateStr);
}
public static Date parseDate2(String dateStr) throws ParseException {
SimpleDateFormat sdf = threadLocalMap.get(DATE_FORMAT_2).get();
return sdf.parse(dateStr);
}
}
DateUtil3
第三类解决方案:
项目记事【多线程】:关于 SimpledDateFormat 的多线程问题的更多相关文章
- 多线程系列之 java多线程的个人理解(二)
前言:上一篇多线程系列之 java多线程的个人理解(一) 讲到了线程.进程.多线程的基本概念,以及多线程在java中的基本实现方式,本篇主要接着上一篇继续讲述多线程在实际项目中的应用以及遇到的诸多问题 ...
- 多线程系列之 Java多线程的个人理解(一)
前言:多线程常常是程序员面试时会被问到的问题之一,也会被面试官用来衡量应聘者的编程思维和能力的重要参考指标:无论是在工作中还是在应对面试时,多线程都是一个绕不过去的话题.本文重点围绕多线程,借助Jav ...
- [.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(三) 利用多线程提高程序性能(下)
[.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(二) 利用多线程提高程序性能(下) 本节导读: 上节说了线程同步中使用线程锁和线程通知的方式来处理资源共享问题,这 ...
- [.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中)
[.net 面向对象程序设计进阶] (17) 多线程(Multithreading)(二) 利用多线程提高程序性能(中) 本节要点: 上节介绍了多线程的基本使用方法和基本应用示例,本节深入介绍.NET ...
- [.net 面向对象程序设计进阶] (16) 多线程(Multithreading)(一) 利用多线程提高程序性能(上)
[.net 面向对象程序设计进阶] (16) 多线程(Multithreading)(一) 利用多线程提高程序性能(上) 本节导读: 随着硬件和网络的高速发展,为多线程(Multithreading) ...
- Python之FTP多线程下载文件之多线程分块下载文件
Python之FTP多线程下载文件之多线程分块下载文件 Python中的ftplib模块用于对FTP的相关操作,常见的如下载,上传等.使用python从FTP下载较大的文件时,往往比较耗时,如何提高从 ...
- 002_Python多线程相当于单核多线程的论证
很多人都说python多线程是假的多线程!下面进行论证解释: 一. 我们先明确一个概念,全局解释器锁(GIL) Python代码的执行由Python虚拟机(解释器)来控制.Python在设计之初就考虑 ...
- Java多线程父子线程关系 多线程中篇(六)
有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...
- 三、多线程基础-自旋_AQS_多线程上下文
1. 自旋理解 很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题.既然sync ...
随机推荐
- LeetCode Pascal's Triangle Pascal三角形
题意:给一个数字,返回一个二维数组,包含一个三角形. 思路:n=0.1.2都是特例,特别处理.3行以上的的头尾都是1,其他都是依靠上一行的两个数.具体了解Pascal三角形原理. class Solu ...
- windows7桌面小工具打不开的解决方案
将任务管理器中的sidebar.exe结束任务: 将C:\Users\用户名\AppData\Local\Microsoft\Windows Sidebar下的settings.ini的文件名修改为任 ...
- CDOJ 485 UESTC 485 Game (八数码变形,映射,逆cantor展开)
题意:八数码,但是转移的方式是转动,一共十二种,有多组询问,初态唯一,终态不唯一. 题解:初态唯一,那么可以预处理出012345678的所有转移情况,然后将初态对012345678做一个映射,再枚举一 ...
- [视觉] 基于YoloV3的实时摄像头记牌器
基于YoloV3的实时摄像头记牌器 github:https://github.com/aoru45/cards_recognition_recorder_pytorch 最终效果 数据准备 数据获取 ...
- js函数节流和函数防抖
概念解释 函数节流: 频繁触发,但只在特定的时间内才执行一次代码 函数防抖: 频繁触发,但只在特定的时间内没有触发执行条件才执行一次代码 函数节流 函数节流应用的实际场景,多数在监听页面元素滚动事件的 ...
- [转]LLE
原始特征的数量可能很大,或者说样本是处于一个高维空间中,通过映射或变换的方法,降高维数据降低到低维空间中的数据,这个过程叫特征提取,也称降维. 特征提取得基本任务研究从众多特征中求出那些对分类最有效的 ...
- 【线段树分治 线性基】luoguP3733 [HAOI2017]八纵八横
不知道为什么bzoj没有HAOI2017 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路 ...
- Voyager的安装及配置文件
使用代理服务器安装laravel http_proxy=http://localhost:1080 composer create-project --prefer-dist laravel/lara ...
- 好久没写了,总结一下lnux常用的命令(基础)
Linux 1.init 0 关机 2.init 6 重启 3.ls 列出当前目录下的文件 4.cd 切换目录 cd - 切换最近使用的两次目录 5.pwd 查看当前所在的路径 (“-”为用户 ...
- LeetCode939
问题:最小面积矩形 给定在 xy 平面上的一组点,确定由这些点组成的矩形的最小面积,其中矩形的边平行于 x 轴和 y 轴. 如果没有任何矩形,就返回 0. 示例 1: 输入:[[1,1],[1,3], ...