当前位置:网站首页>SimpleDateFormat在多线程环境下存在线程安全问题。
SimpleDateFormat在多线程环境下存在线程安全问题。
2022-06-23 16:51:00 【张 邵】
SimpleDateFormat在多线程环境下存在线程安全问题。
1 SimpleDateFormat .parse() 方法的线程安全问题
1.1 错误示例
错误使用SimpleDateFormat .parse()的代码如下:
import java.text.SimpleDateFormat;
public class SimpleDateFormatTest {
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
/** * SimpleDateFormat线程不安全,没有保证线程安全(没有加锁)的情况下,禁止使用全局SimpleDateFormat,否则报错 NumberFormatException * * private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); */
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() -> {
try {
// 错误写法会导致线程安全问题
System.out.println(Thread.currentThread().getName() + "--" + SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00"));
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}

1.2 非线程安全原因分析
查看源码中可以看到:SimpleDateFormat继承DateFormat类,SimpleDateFormat转换日期是通过继承自DateFormat类的Calendar对象来操作的,Calendar对象会被用来进行日期-时间计算,既被用于format方法也被用于parse方法。

SimpleDateFormat 的 parse(String source) 方法 会调用继承自父类的 DateFormat 的 parse(String source) 方法

DateFormat 的 parse(String source) 方法会调用SimpleDateFormat中重写的 parse(String text, ParsePosition pos) 方法,该方法中有个地方需要关注

SimpleDateFormat 中重写的 parse(String text, ParsePosition pos) 方法中调用了 establish(calendar) 这个方法:

该方法中调用了 Calendar 的 clear() 方法

可以发现整个过程中Calendar对象它并不是线程安全的,如果,a线程将calendar清空了,calendar 就没有新值了,恰好此时b线程刚好进入到parse方法用到了calendar对象,那就会产生线程安全问题了!
正常情况下:

非线程安全的流程:

1.3 解决方法
方法1:每个线程都new一个SimpleDateFormat
import java.text.SimpleDateFormat;
public class SimpleDateFormatTest {
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() -> {
try {
// 每个线程都new一个
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(Thread.currentThread().getName() + "--" + simpleDateFormat.parse("2020-06-01 11:35:00"));
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}
方式2:synchronized等方式加锁
public class SimpleDateFormatTest {
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() -> {
try {
synchronized(SIMPLE_DATE_FORMAT) {
System.out.println(Thread.currentThread().getName() + "--" + SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00"));
}
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}
方式3:使用ThreadLocal 为每个线程创建一个独立变量
import java.text.DateFormat;
import java.text.SimpleDateFormat;
public class SimpleDateFormatTest {
private static final ThreadLocal < DateFormat > SAFE_SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() - > new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
for (int i = 0; i < 20; ++i) {
Thread thread = new Thread(() - > {
try {
System.out.println(Thread.currentThread().getName() + "--" + SAFE_SIMPLE_DATE_FORMAT.get().parse("2020-06-01 11:35:00"));
} catch (Exception e) {
e.printStackTrace();
}
}, "Thread-" + i);
thread.start();
}
}
}
2 SimpleDateFormat .format() 方法的线程安全问题
2.1 错误示例
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SimpleDateFormatTest {
// 时间格式化对象
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) throws InterruptedException {
// 创建线程池执行任务
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue < > (1000));
for (int i = 0; i < 1000; i++) {
int finalI = i;
// 执行任务
threadPool.execute(new Runnable() {
@
Override
public void run() {
Date date = new Date(finalI * 1000); // 得到时间对象
formatAndPrint(date); // 执行时间格式化
}
});
}
threadPool.shutdown(); // 线程池执行完任务之后关闭
}
/** * 格式化并打印时间 */
private static void formatAndPrint(Date date) {
String result = simpleDateFormat.format(date); // 执行格式化
System.out.println("时间:" + result); // 打印最终结果
}
}

从上述结果可以看出,程序的打印结果竟然有重复内容的,正确的情况应该是没有重复的时间才对。
2.2 非线程安全原因分析
为了找到问题所在,查看 SimpleDateFormat 中 format 方法的源码来排查一下问题,format 源码如下:

从上述源码可以看出,在执行 SimpleDateFormat.format() 方法时,会使用 calendar.setTime() 方法将输入的时间进行转换,那么我们想想一下这样的场景:
1、 线程1执行了calendar.setTime(date)方法,将用户输入的时间转换成了后面格式化时所需要的时间;
2、 线程1暂停执行,线程2得到CPU时间片开始执行;
3、 线程2执行了calendar.setTime(date)方法,对时间进行了修改;
4、 线程2暂停执行,线程1得出CPU时间片继续执行,因为线程1和线程2使用的是同一对象,而时间已经被线程2修改了,所以此时当线程1继续执行的时候就会出现线程安全的问题了;
正常的情况下,程序的执行是这样的:

非线程安全的执行流程是这样的:

2.3 解决方法
同样有三种解决方法
方法1:每个线程都new一个SimpleDateFormat
public class SimpleDateFormatTest {
public static void main(String[] args) throws InterruptedException {
// 创建线程池执行任务
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue < > (1000));
for (int i = 0; i < 1000; i++) {
int finalI = i;
// 执行任务
threadPool.execute(new Runnable() {
@
Override
public void run() {
// 得到时间对象
Date date = new Date(finalI * 1000);
// 执行时间格式化
formatAndPrint(date);
}
});
}
// 线程池执行完任务之后关闭
threadPool.shutdown();
}
/** * 格式化并打印时间 */
private static void formatAndPrint(Date date) {
String result = new SimpleDateFormat("mm:ss").format(date); // 执行格式化
System.out.println("时间:" + result); // 打印最终结果
}
}
方式2:synchronized等方式加锁
所有的线程必须排队执行某些业务才行,这样无形中就降低了程序的运行效率了
public class SimpleDateFormatTest {
// 时间格式化对象
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) throws InterruptedException {
// 创建线程池执行任务
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue < > (1000));
for (int i = 0; i < 1000; i++) {
int finalI = i;
// 执行任务
threadPool.execute(new Runnable() {
@
Override
public void run() {
Date date = new Date(finalI * 1000); // 得到时间对象
formatAndPrint(date); // 执行时间格式化
}
});
}
// 线程池执行完任务之后关闭
threadPool.shutdown();
}
/** * 格式化并打印时间 */
private static void formatAndPrint(Date date) {
// 执行格式化
String result = null;
// 加锁
synchronized(SimpleDateFormatTest.class) {
result = simpleDateFormat.format(date);
}
// 打印最终结果
System.out.println("时间:" + result);
}
}
方式3:使用ThreadLocal 为每个线程创建一个独立变量
public class SimpleDateFormatTest {
// 创建 ThreadLocal 并设置默认值
private static ThreadLocal < SimpleDateFormat > dateFormatThreadLocal = ThreadLocal.withInitial(() - > new SimpleDateFormat("mm:ss"));
public static void main(String[] args) {
// 创建线程池执行任务
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue < > (1000));
// 执行任务
for (int i = 0; i < 1000; i++) {
int finalI = i;
// 执行任务
threadPool.execute(() - > {
Date date = new Date(finalI * 1000); // 得到时间对象
formatAndPrint(date); // 执行时间格式化
});
}
threadPool.shutdown(); // 线程池执行完任务之后关闭
}
/** * 格式化并打印时间 */
private static void formatAndPrint(Date date) {
String result = dateFormatThreadLocal.get().format(date); // 执行格式化
System.out.println("时间:" + result); // 打印最终结果
}
}
边栏推荐
- 芯片原厂必学技术之理论篇(4-1)时钟技术、复位技术
- Easygbs playback screen is continuously loading. Troubleshooting
- Interpretation of eventbus source code
- The principle of MySQL index algorithm and the use of common indexes
- Alien world, real presentation, how does the alien version of Pokemon go achieve?
- 如何设计一个秒杀系统?
- Best practices cloud development cloudbase content audit capability
- C. Phoenix and Towers-Codeforces Global Round 14
- Go unit test
- Goframe framework: basic auth Middleware
猜你喜欢

论文阅读 (48):A Library of Optimization Algorithms for Organizational Design

qYKVEtqdDg

对抗攻击与防御 (1):图像领域的对抗样本生成

论文阅读 (51):Integration of a Holonic Organizational Control Architecture and Multiobjective...

qYKVEtqdDg

torch学习(一):环境配置

酒店入住时间和离店时间的日期选择
![[30. concatenate substrings of all words]](/img/e7/453c8524a23fbb7501e85140547ce1.png)
[30. concatenate substrings of all words]

暂停更新公告—行走的皮卡丘

org.apache.ibatis.binding.BindingException: Invalid bound statement (not found):...
随机推荐
How to design a seckill system?
Explanation of the principle and code implementation analysis of rainbow docking istio
视频异常检测数据集 (ShanghaiTech)
Vulnerability in McAfee epolicy orchestrator
High availability solution practice of mongodb advanced applications (4)
Similarities and differences between Chinese and American electronic signature SaaS
Nodejs implements multi process
Cryptography involved in IOT device end
What if the website is poisoned
Wechat applet: time selector for the estimated arrival date of the hotel
How to use R language to draw scatter diagram
Answer 02: why can Smith circle "allow left string and right parallel"?
. Net cloud native architect training camp (responsibility chain mode) -- learning notes
Ner's past, present and future Overview - Future
JS custom error
AMQP协议
论文阅读 (57):2-hydr_Ensemble: Lysine 2-Hydroxyisobutyrylation Identification with Ensemble Method (任务)
Add new members to the connector family! Scenario connector helps enterprises comprehensively improve the operational efficiency of business systems
微信小程序:酒店预计到店日期的时间选择器
Android kotlin exception handling