SimpleDateFormat线程不安全及解决办法
来源:互联网 发布:银联数据待遇怎么样 编辑:程序博客网 时间:2024/06/05 14:41
一. 为什么SimpleDateFormat不是线程安全的?
源码:
public class SimpleDateFormat extends DateFormat {public Date parse(String text, ParsePosition pos){ calendar.clear(); // Clears all the time fields // other logic ... Date parsedDate = calendar.getTime(); } }
public abstract class DateFormat extends Format { protected Calendar calendar; public Date parse(String source) throws ParseException{ ParsePosition pos = new ParsePosition(0); Date result = parse(source, pos); if (pos.index == 0) throw new ParseException("Unparseable date: \"" + source + "\"" , pos.errorIndex); return result; } }
如果我们把SimpleDateFormat定义成static成员变量,那么多个thread之间会共享这个sdf对象, 所以Calendar对象也会共享。
假定线程A和线程B都进入了parse(text, pos) 方法, 线程B执行到calendar.clear()后,线程A执行到calendar.getTime(), 那么就会有问题。
二. 问题重现:
public class DateFormatTest { private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" }; public static void main(String[] args) { for (int i = 0; i < date.length; i++) { final int temp = i; new Thread(new Runnable() { @Override public void run() { try { while (true) { String str1 = date[temp]; String str2 = sdf.format(sdf.parse(str1)); System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2); if(!str1.equals(str2)){ throw new RuntimeException(Thread.currentThread().getName() + ", Expected " + str1 + " but got " + str2); } } } catch (Exception e) { throw new RuntimeException("parse failed", e); } } }).start(); } } }
创建三个进程, 使用静态成员变量SimpleDateFormat的parse和format方法,然后比较经过这两个方法折腾后的值是否相等:
程序如果出现以下错误,说明传入的日期就串掉了,即SimpleDateFormat不是线程安全的:
Exception in thread "Thread-0" java.lang.RuntimeException: parse failed at cn.test.DateFormatTest$1.run(DateFormatTest.java:27) at java.lang.Thread.run(Thread.java:662) Caused by: java.lang.RuntimeException: Thread-0, Expected 01-Jan-1999 but got 01-Jan-2000 at cn.test.DateFormatTest$1.run(DateFormatTest.java:22) ... 1 more
三. 解决方案:
- 解决方案a:
将SimpleDateFormat定义成局部变量:
SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); String str1 = "01-Jan-2010"; String str2 = sdf.format(sdf.parse(str1));
缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。
2. 解决方案b:
加一把线程同步锁:synchronized(lock)
public class SyncDateFormatTest { private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" }; public static void main(String[] args) { for (int i = 0; i < date.length; i++) { final int temp = i; new Thread(new Runnable() { @Override public void run() { try { while (true) { synchronized (sdf) { String str1 = date[temp]; Date date = sdf.parse(str1); String str2 = sdf.format(date); System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2); if(!str1.equals(str2)){ throw new RuntimeException(Thread.currentThread().getName() + ", Expected " + str1 + " but got " + str2); } } } } catch (Exception e) { throw new RuntimeException("parse failed", e); } } }).start(); } } }
缺点:性能较差,每次都要等待锁释放后其他线程才能进入
3. 解决方案c: (推荐)
使用ThreadLocal: 每个线程都将拥有自己的SimpleDateFormat对象副本。
写一个工具类:
public class DateUtil { private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>(); public static Date parse(String str) throws Exception { SimpleDateFormat sdf = local.get(); if (sdf == null) { sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); local.set(sdf); } return sdf.parse(str); } public static String format(Date date) throws Exception { SimpleDateFormat sdf = local.get(); if (sdf == null) { sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); local.set(sdf); } return sdf.format(date); } }
测试代码:
public class ThreadLocalDateFormatTest { private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" }; public static void main(String[] args) { for (int i = 0; i < date.length; i++) { final int temp = i; new Thread(new Runnable() { @Override public void run() { try { while (true) { String str1 = date[temp]; Date date = DateUtil.parse(str1); String str2 = DateUtil.format(date); System.out.println(str1 + "," + str2); if(!str1.equals(str2)){ throw new RuntimeException(Thread.currentThread().getName() + ", Expected " + str1 + " but got " + str2); } } } catch (Exception e) { throw new RuntimeException("parse failed", e); } } }).start(); } } }
补充:java.util.Random是线程安全的,不过多个线程的时候要同时公用一个生成器,这里可以使用ThreadLocalRandom为每个线程生成一个随机生成器
阅读全文
0 0
- SimpleDateFormat线程不安全及解决办法
- SimpleDateFormat线程不安全及解决办法
- SimpleDateFormat线程不安全及解决办法
- SimpleDateFormat线程不安全及解决办法
- Java SimpleDateFormat 线程不安全问题及解决方法
- java SimpleDateFormat线程不安全
- SimpleDateFormat 线程不安全
- SimpleDateFormat线程不安全
- simpledateformat线程不安全解决方案
- SimpleDateFormat线程不安全问题
- SimpleDateFormat线程不安全
- SimpleDateFormat线程不安全
- simpledateformat线程不安全解决方案
- SimpleDateFormat线程不安全 【坑】
- SimpleDateFormat非线程不安全
- Java之——SimpleDateFormat 线程不安全问题及解决方法
- 解决SimpleDateFormat的线程不安全问题的方法:ThreadLocal
- 关于jdk1.7的SimpleDateFormat类线程不安全
- 线程池简单实现
- select标签及其标签元素
- 如何找到占用cpu最高的java线程?tomcat所在线程是守护线程吗?如何在linux中断一个Java进程中的线程,如何让一个守护线程不被结束
- pycharm入门实例
- Hibernate 调用本地多个数据库数据
- SimpleDateFormat线程不安全及解决办法
- git 命令详解
- oracle install manually(手动建库)
- 树莓派3+树莓派系统安装ros
- LinuxStudyNote(12)-Linux常用命令(2)-文件处理命令(7)mv文件目录剪切及更名
- Linux下文件属性(drwxr-xr-x)详解以及(-rwxrwxrwx=777)
- __asm__和__volatile__
- hdu 1069 Monkey and Banana
- 欢迎使用CSDN-markdown编辑器