当前位置:网站首页>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); // 打印最终结果
}
}
边栏推荐
- Digital intelligent supply chain collaboration solution for new energy industry
- 13. IP address and subnet partitioning (VLSM)
- [30. concatenate substrings of all words]
- Lighthouse open source application practice: o2oa
- Intelligent supply chain collaborative management solution for logistics industry
- Best practices cloud development cloudbase content audit capability
- Vulnerability in McAfee epolicy orchestrator
- How to quickly obtain and analyze the housing price in your city?
- This time, thoroughly understand the SparseArray implementation principle
- Answer 01: why can Smith circle "allow left string and right parallel"?
猜你喜欢

qYKVEtqdDg

JSON - learning notes (message converter, etc.)

论文阅读 (55):Dynamic Multi-Robot Task Allocation under Uncertainty and Temporal Constraints

Self supervised learning (SSL)

qYKVEtqdDg

【win10 VS2019 opencv4.6 配置参考】

Alien world, real presentation, how does the alien version of Pokemon go achieve?

Wechat applet: time selector for the estimated arrival date of the hotel

CRMEB 二开短信功能教程
![[mae]masked autoencoders mask self encoder](/img/08/5ab2b0d5b81c723919046699bb6f6d.png)
[mae]masked autoencoders mask self encoder
随机推荐
Cryptography involved in IOT device end
MySQL - reasons for using repeatable read
Skills that all applet developers should know: applying applet components
Also using copy and paste to create test data, try the data assistant!
手机开户流程是什么?现在网上开户安全么?
Nanny level teaching! Take you to play with time complexity and space complexity!
Similarities and differences between Chinese and American electronic signature SaaS
Three functional forms of intelligent switch
C. Phoenix and Towers-Codeforces Global Round 14
Réponse 02: pourquoi le cercle Smith peut - il "se sentir haut et bas et se tenir à droite et à droite"?
Date selection of hotel check-in time and check-out time
解答02:Smith圆为什么能“上感下容 左串右并”?
Script to view the execution of SQLSERVER database stored procedures
JS regular verification time test() method
How to create a three elimination game
Go unit test
[WebSocket] 开发在线客服系统知识点-websocket返回状态码的含义
Ctfshow PHP features
《AN4190应用笔记 天线选择指南》——天线理论2
论文阅读 (57):2-hydr_Ensemble: Lysine 2-Hydroxyisobutyrylation Identification with Ensemble Method (任务)