SimpleDateFormat線程不安全示例及其解決方法

咱們能夠用java.text.SimpleDateFormat類完成日期的轉換和格式化操做,如:java

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中看到以下一句話。git

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沒有同步。apache

建議爲每個線程建立獨立的format對象。安全

若是多個線程併發訪問一個format,那麼,必定要在外部實現同步(synchronized)。多線程

也就是說,併發

在多線程下咱們須要作些額外的保護措施,去保證其正確處理,不然是不安全的。測試

讓咱們一塊兒來看一下,多線程下會出現什麼問題:spa

線程不安全示例

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());
		}
	}
}

運行結果主要包含以下幾個錯誤:code

  • 無異常,日期解析出現錯誤
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中,已經對時間進行着改造,也已經逐漸向不可變和線程安全方向靠攏,有興趣的讀者能夠嘗試一下。

相關文章
相關標籤/搜索