废话不多说,先上代码
package z.farrell.framework.utils;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
public class DateUtilTest {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Test
public void dateTest(){
ExecutorService executorService = Executors.newFixedThreadPool(500);
for (int i = 0; i < 500; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
try {
DATE_FORMAT.parse("2016-12-12 12:12:12");
} catch (ParseException e) {
e.printStackTrace();
}
}
}
});
}
}
}
代码运行结果:
Exception in thread "pool-1-thread-2" java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:453)
at java.lang.Long.parseLong(Long.java:483)
at java.text.DigitList.getLong(DigitList.java:194)
at java.text.DecimalFormat.parse(DecimalFormat.java:1316)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
at java.text.DateFormat.parse(DateFormat.java:355)
at z.farrell.framework.utils.DateUtilTest$1.run(DateUtilTest.java:25)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "pool-1-thread-28" java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:453)
at java.lang.Long.parseLong(Long.java:483)
at java.text.DigitList.getLong(DigitList.java:194)
at java.text.DecimalFormat.parse(DecimalFormat.java:1316)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1793)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1455)
at java.text.DateFormat.parse(DateFormat.java:355)
at z.farrell.framework.utils.DateUtilTest$1.run(DateUtilTest.java:25)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
这种方式,相信很多程序员都是经常使用的办法,可惜结果不如人意,当偶尔出现异常时,打印出来的日志格式依然是正确的
那么这到底是怎么回事呢?事实上就是,日期格式是正确的,但就是解析失败!
那么导致这种问题的原因是什么呢?这么奇葩的问题,想来也只有在高并发可以解释了。
接下来我们分析一下SimpleDateFormat的源码,果然在源码的注释中有这么一段话。
* <h4><a name="synchronization">Synchronization</a></h4>
*
* <p>
* 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.
*
* @see <a href="http://java.sun.com/docs/books/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>
* @see java.util.Calendar
* @see java.util.TimeZone
* @see DateFormat
* @see DateFormatSymbols
* @author Mark Davis, Chen-Lieh Huang, Alan Liu
*/
public class SimpleDateFormat extends DateFormat {
这段话的意思翻译为中文的意思就是:日期格式化的类是非同步的,建议为每一个线程创建独立的格式化实例,如果多个线程并发访问同一个格式化实例,就必须在外部添加同步机制。
由于错误的将SimpleDateFormat的单个实例放置于高并发的环境下,并且没有任何同步机制,于是导致上面的异常
修改代码
package z.farrell.framework.utils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.Test;
public class DateUtilTest {
@Test
public void dateTest1(){
ExecutorService executorService = Executors.newFixedThreadPool(500);
for (int i = 0; i < 500; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
try {
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-12-12 12:12:12");
} catch (ParseException e) {
e.printStackTrace();
}
}
}
});
}
}
}
果然,上面的异常就不再出现了
小结
高并发所引发的问题往往很难解决,因为它无法稳定重现,如比本文中的问题,如果不是在高并发的情况下,可能你的程序运行半年甚至更久,都不一定能出现几次解析失败的异常。就算偶尔出现,你也可能任务是日期格式错误,从而忽略掉它本身的机制。
同样的功能,不同的人写出的代码质量确实有很大的差距。就算本文中这么简单的一个日期工具类,一不小心就可能造成意想不到的错误,幸好JDK的代码写的足够规范,大部分的线程安全性都写的很清楚,这才找到问题的根源。
相信当下不少的程序猿们认为当下自己做的项目或者写的代码没有什么技术含量,以至于每日浑浑噩噩,缺乏激情。那么本文就告诉你一个道理,不是因为项目让你发光发热,而是因为你才让项目发光发热。