文档章节

SimpleDateFormat线程不安全示例及其解决方法

王孟君
 王孟君
发布于 2016/11/20 13:30
字数 1739
阅读 107
收藏 6

我们可以用java.text.SimpleDateFormat类完成日期的转换和格式化操作,如:

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
			"yyyyMMdd");

	public Date parseDate(String value) throws ParseException {
		return SIMPLE_DATE_FORMAT.parse(value);
	}

}

import java.text.ParseException;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class Main {

	public static void main(String[] args) throws ParseException {
		SimpleDateFormatExample example = new SimpleDateFormatExample();
		Date date = example.parseDate("20161118");
		//Fri Nov 18 00:00:00 CST 2016
		System.out.println(date);
	}
}

但是,同时,我们也能从java.text.SimpleDateFormat类的javadoc中看到如下一句话。

 Date formats are not synchronized.
 It is recommended to create separate format instances for each thread.
 If multiple threads access a format concurrently, it must be synchronized externally.

Date formats没有同步。

建议为每一个线程创建独立的format对象。

如果多个线程并发访问一个format,那么,一定要在外部实现同步(synchronized)。

也就是说,

在多线程下我们需要做些额外的保护措施,去保证其正确处理,否则是不安全的。

让我们一起来看一下,多线程下会出现什么问题:

线程不安全示例

package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
			"yyyyMMdd");

	public Date parseDate(String value) throws ParseException {
		return SIMPLE_DATE_FORMAT.parse(value);
	}

}

多线程测试示例:

package my.format;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Main {

	public static void main(String[] args) throws InterruptedException,
			ExecutionException, ParseException {

		int availableProcessors = Runtime.getRuntime().availableProcessors();
		ExecutorService exec = Executors
				.newFixedThreadPool(availableProcessors);

		List<Future<Date>> results = new ArrayList<>();

		final SimpleDateFormatExample sdf = new SimpleDateFormatExample();
		Callable<Date> parseDateTask = new Callable<Date>() {
			public Date call() throws Exception {
				return sdf.parseDate("20161118");
			}
		};

		for (int i = 0; i < 10; i++) {
			results.add(exec.submit(parseDateTask));
		}
		
		exec.shutdown();

		/**
		 * 查看结果
		 */
		for (Future<Date> result : results) {
			System.out.println(result.get());
		}
	}
}

运行结果主要包含如下几个错误:

  • 无异常,日期解析出现错误
Tue Nov 18 00:00:00 CST 2200
Tue Nov 18 00:00:00 CST 2200
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Thu Nov 18 00:00:00 CST 1
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
  • 有异常,java.lang.NumberFormatException

如:

Fri Nov 18 00:00:00 CST 2016
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: ""
	at java.util.concurrent.FutureTask.report(Unknown Source)
	at java.util.concurrent.FutureTask.get(Unknown Source)
	at my.format.Main.main(Main.java:40)
Caused by: java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(Unknown Source)
	at java.lang.Long.parseLong(Unknown Source)
	at java.lang.Long.parseLong(Unknown Source)
	at java.text.DigitList.getLong(Unknown Source)
	at java.text.DecimalFormat.parse(Unknown Source)
	at java.text.SimpleDateFormat.subParse(Unknown Source)
	at java.text.SimpleDateFormat.parse(Unknown Source)
	at java.text.DateFormat.parse(Unknown Source)
	at my.format.SimpleDateFormatExample.parseDate(SimpleDateFormatExample.java:14)
	at my.format.Main$1.call(Main.java:27)
	at my.format.Main$1.call(Main.java:1)
	at java.util.concurrent.FutureTask.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

再如:

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.NumberFormatException: For input string: "1111.E1111E22"
	at java.util.concurrent.FutureTask.report(Unknown Source)
	at java.util.concurrent.FutureTask.get(Unknown Source)
	at my.format.Main.main(Main.java:40)
Caused by: java.lang.NumberFormatException: For input string: "1111.E1111E22"
	at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
	at java.lang.Double.parseDouble(Unknown Source)
	at java.text.DigitList.getDouble(Unknown Source)
	at java.text.DecimalFormat.parse(Unknown Source)
	at java.text.SimpleDateFormat.subParse(Unknown Source)
	at java.text.SimpleDateFormat.parse(Unknown Source)
	at java.text.DateFormat.parse(Unknown Source)
	at my.format.SimpleDateFormatExample.parseDate(SimpleDateFormatExample.java:14)
	at my.format.Main$1.call(Main.java:27)
	at my.format.Main$1.call(Main.java:1)
	at java.util.concurrent.FutureTask.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

 

那么问题来了,如何保证运行正常呢?

解决方法

其实,从SimpleDateFormat的javadoc中已经看到有处理的方法了。

 Date formats are not synchronized.
 It is recommended to create separate format instances for each thread.
 If multiple threads access a format concurrently, it must be synchronized externally.

接下来,先从这个描述信息给出相关的解决方法。

每次都新建SimpleDateFormat对象

改造SimpleDateFormatExample类,如:

package my.format;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	public Date parseDate(String value) throws ParseException {
		return new SimpleDateFormat(
				"yyyyMMdd").parse(value);
	}

}

执行上述Main.java类,得到正确结果:

Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

访问format时,添加synchronized

改造SimpleDateFormatExample类,如:

package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
			"yyyyMMdd");

	public Date parseDate(String value) throws ParseException {
		synchronized (SIMPLE_DATE_FORMAT) {
			return SIMPLE_DATE_FORMAT.parse(value);
		}
	}

}

或者在使用format对象的方法前添加synchronized修饰,如:

package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	private static final DateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(
			"yyyyMMdd");

	public synchronized Date parseDate(String value) throws ParseException {
		return SIMPLE_DATE_FORMAT.parse(value);
	}

}

同样,执行上述Main.java类,可以得到正确结果:

Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

使用TheadLocal

改造SimpleDateFormatExample类,如:

package my.format;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	private static final ThreadLocal<DateFormat> THREAD_LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() {
		protected DateFormat initialValue() {
			return new SimpleDateFormat("yyyyMMdd");
		}
	};

	public Date parseDate(String value) throws ParseException {
		return THREAD_LOCAL_DATE_FORMAT.get().parse(value);
	}

}

同样,执行上述Main.java类,可以得到正确结果:

Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

使用FastDateFormat

FastDateFormat类在Apache Common Langs包下面, 该类是线程安全的。

如果是Maven工程,其添加依赖包如下:

		<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
			<version>3.5</version>
		</dependency>

改造SimpleDateFormatExample类,如:

	private static final FastDateFormat SIMPLE_DATE_FORMAT = FastDateFormat
			.getInstance("yyyyMMdd");

完成的类为:

package my.format;

import java.text.ParseException;
import java.util.Date;

import org.apache.commons.lang3.time.FastDateFormat;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	private static final FastDateFormat SIMPLE_DATE_FORMAT = FastDateFormat
			.getInstance("yyyyMMdd");

	public Date parseDate(String value) throws ParseException {
		return SIMPLE_DATE_FORMAT.parse(value);
	}

}

同样,执行上述Main.java类,可以得到正确结果:

Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

使用Joda Time

DateTimeFormatter 类Joda-Time包下面, 该类是线程安全的。

如果是Maven工程,其添加依赖包如下:

		<!-- https://mvnrepository.com/artifact/joda-time/joda-time -->
		<dependency>
			<groupId>joda-time</groupId>
			<artifactId>joda-time</artifactId>
			<version>2.9.6</version>
		</dependency>

改造SimpleDateFormatExample类,如:

package my.format;

import java.text.ParseException;
import java.util.Date;

import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

/**
 * 
 * @author wangmengjun
 *
 */
public class SimpleDateFormatExample {

	private static final DateTimeFormatter SIMPLE_DATE_FORMAT = DateTimeFormat
			.forPattern("yyyyMMdd");

	public Date parseDate(String value) throws ParseException {
		return SIMPLE_DATE_FORMAT.parseDateTime(value).toDate();
	}

}

同样,执行上述Main.java类,可以得到正确结果:

Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016
Fri Nov 18 00:00:00 CST 2016

小结

本文首先给出了一个使用SimpleDateFormat的简单示例;接着,给出了一个在多线程下使用SimpleDateFormat出现问题的例子;紧接着又给出了几个解决SimpleDateFormat线程不安全使用的例子。

现在,Java 8中,已经对时间进行着改造,也已经逐渐向不可变和线程安全方向靠拢,有兴趣的读者可以尝试一下。

© 著作权归作者所有

共有 人打赏支持
王孟君

王孟君

粉丝 218
博文 94
码字总数 221044
作品 0
杭州
高级程序员
线程不安全的SimpleDateFormat

8.5 SimpleDateFormat是线程不安全的 SimpleDateFormat是Java提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是由于它是线程不安全的,多线程公用一个SimpleDateFormat实...

今天你不奋斗明天你就落后
2017/12/14
0
0
ThreadLocal 解决SimpleDateFormat非线程安全

大致意思:Tim Cull碰到一个SimpleDateFormat带来的严重的性能问题,该问题主要有SimpleDateFormat引发,创建一个 SimpleDateFormat实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短...

凯文加内特
2014/03/20
0
0
SimpleDateFormat 的使用注意点(线程安全问题)

Bug: Call to method of static Java.text.DateFormat Pattern id: STCALINVOKEONSTATICDATEFORMATINSTANCE, type: STCAL, category: MT_CORRECTNESS As the JavaDoc states, DateFormats ar......

凯文加内特
2016/05/18
223
0
Java并发编程笔记之SimpleDateFormat源码分析

SimpleDateFormat 是 Java 提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是由于它是线程不安全的,多线程公用一个 SimpleDateFormat 实例对日期进行解析或者格式化会导...

狂小白
07/12
0
0
Java并发编程之SimpleDateFormat源码分析

SimpleDateFormat 是 Java 提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是由于它是线程不安全的,多线程公用一个 SimpleDateFormat 实例对日期进行解析或者格式化会导...

狂小白
07/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Shell编程(expect同步文件、指定host和同步文件、构建文件分发系统、批量执行命令)

expect脚本同步文件 需求:自动同步文件 实验准备: A机器:192.168.248.130 B机器:192.168.248.129 实现: 1.A机器编写4.expect脚本文件,内容如下所示: #!/usr/bin/expectset passwd "...

蛋黄_Yolks
16分钟前
0
0
ppwjs之bootstrap颜色:背景颜色

<!DOCTYPT html><html><head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>ppwjs欢迎您</title><link rel="icon" href="/favicon.ico" ......

ppwjs
17分钟前
0
0
Ubuntu与 Fedora之对比

大家好。今天我将重点介绍两个流行的Linux发行版之间的一些特性和差异; Ubuntu 18.04和Fedora 28。它们都有自己的包管理; Ubuntu使用DEB,而Fedora使用RPM,但它们都具有相同的桌面环境(GNO...

linuxprobe16
20分钟前
0
0
线性代数入门

线性代数的概念对于理解机器学习背后的原理非常重要,尤其是在深度学习领域中。它可以帮助我们更好地理解算法内部到底是怎么运行的,借此,我们就能够更好的做出决策。所以,如果你真的希望了...

牛奋Debug
昨天
2
0
开发5分钟,调试2小时 - 该如何debug?

几年来我在答疑群、论坛、公众号、知乎回答的各种问题,没有一万也有八千。其中有三分之二以上都是在帮人看报错,帮人 debug(调试代码)。 可以说,会不会 debug,有没有 debug 的意识,懂不...

crossin
昨天
3
1

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部