simpleDateFormat執行緒不安全
阿新 • • 發佈:2018-12-04
simpleDateFormat是我們比較常用的日期轉換類,但是它是一個執行緒不安全的類。 舉例證明
public class DateFormatExample1 { //請求總數 private static int clientTotal = 1000; //同時允許執行的執行緒總數 private static int threadTotal = 20; private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); public static void main(String[] args) throws InterruptedException { final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < clientTotal; i++) { executorService.execute(new Runnable() { @Override public void run() { try { semaphore.acquire(); try { simpleDateFormat.parse("2018-09-26"); } catch (ParseException e) { e.printStackTrace(); } semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } countDownLatch.countDown(); } }); } countDownLatch.await(); executorService.shutdown(); } }
這段程式碼在執行的過程中,會報錯。
Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-8" Exception in thread "pool-1-thread-9" java.lang.NumberFormatException: For input string: "E.199E1" at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(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 com.guoy.concurrency.commonUnsafe.DateFormatExample1$1.run(DateFormatExample1.java:31) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source) java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) at sun.misc.FloatingDecimal.parseDouble(Unknown Source) at java.lang.Double.parseDouble(Unknown Source)
定義一個靜態的SimpleDateFormat例項,是大家在日期工具類中比較通常的寫法,但是這種寫法在多執行緒的情況下就會丟擲異常。解決方案有很多種:
- 方法一:在每次使用的時候建立SimpleDateFormat的區域性變數,缺點:會建立大量的SimpleDateFormat例項,程式碼不夠簡潔優雅。
- 方法二:其實還有另外一種寫法:使用ThradLocal。 ThreadLocal是一個本地執行緒副本變數工具類。主要用於將私有執行緒和該執行緒存放的副本物件做一個對映,各個執行緒之間的變數互不干擾,在高併發場景下,可以實現無狀態的呼叫,適合於各個執行緒依賴不同的變數值完成操作的場景。
舉例:
package com.guoy.concurrency.commonUnsafe;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
//thread not safe
public class DateFormatExample3 {
//請求總數
private static int clientTotal = 1000;
//同時允許執行的執行緒總數
private static int threadTotal = 20;
private static ThreadLocal<SimpleDateFormat> simpleDateFormat = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < clientTotal; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
try {
simpleDateFormat.get().parse("2018-09-26");
} catch (ParseException e) {
e.printStackTrace();
}
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
}
}
- 方法三:使用joda-time(推薦使用)
引入maven依賴
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class DateFormatExample2 {
//請求總數
private static int clientTotal = 1000;
//同時允許執行的執行緒總數
private static int threadTotal = 20;
private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd");
public static void main(String[] args) throws InterruptedException {
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < clientTotal; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
semaphore.acquire();
DateTime.parse("2018-12-04", dateTimeFormatter).toDate();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
}
});
}
countDownLatch.await();
executorService.shutdown();
}