阿里巴巴java开发手册强制要求:
5. 【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。(org.apache.commons.lang3.time.DateUtils)
因为SimpleDateFormat 继承DateFormat,DateFormat中定义了属性calendar。SimpleDateFormat 里边parse(..)方法和format(..)方法都使用到了calendar,并且使用过程中,对于cal.clear(),cal.set*(..)都没有添加同步锁。所以SimpleDateFormat类的对象并不适合在多线程中共用。
如下所示:
多线程中使用SimpleDateFormat:
可以在每个线程中new一个自己的SimpleDateFormat对象。(对象无法复用,频繁的new和回收)
如果想在多线程中_共用SimpleDateFormat的对象,必须加锁。(synchronized导致降低多线程的效率)
可以使用import org.apache.commons.lang3.time.DateUtils进行一些时间的计算。
可以使用import org.apache.commons.lang3.time.DateFormatUtils进行日期和字符串的转换。
阿里巴巴java开发手册推荐使用ThreadLocal
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
后边有代码示例。
注意:
6. 【强制】必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,
如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。
(如果使用代理模式和工厂模式使用线程池的话)尽量在代理中使用 try-finally 块进行回收。
正例:
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
如果通过new ThreadPoolExecutor(..)创建线程池的话,可以等线程执行完毕以后对ThreadLocal进行回收:threadLocal.remove();。或直接关闭线程池:threadPool.shutDown();
ThreadLocal
public static void main(String[] args) {
ThreadLocal localDateFormat = new ThreadLocal();
// 定义一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 10, 10L, TimeUnit.SECONDS,
new LinkedBlockingQueue(20),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 线程计数器
CountDownLatch main = new CountDownLatch(12);
Runnable runnable = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
SimpleDateFormat dateFormat = localDateFormat.get();
if (null == dateFormat) {
dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
localDateFormat.set(dateFormat);
System.out.println(name+" 线程创建一个新的SimpleDateFormat");
} else {
System.out.println(name+" 线程复用上次使用过的SimpleDateFormat");
}
// 线程任务执行完毕,主线程计数器减1。
main.countDown();
}
};
// 开辟线程执行任务
for (int i = 0; i < 12; i++) {
threadPool.execute(runnable);
if (i == 3 || i == 7) {
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println("=================================");
}
}
}
}
try {
// 线程阻塞,等到线程计数器归零后继续执行。
main.await();
System.out.println("所有线程任务执行完毕,回收localDateFormat。");
localDateFormat.remove();
System.out.println("线程池不用了。关闭线程池。如果之前没有对localDateFormat进行回收,线程池关闭后,线程里的localDateFormat会被回收。");
threadPool.shutdown();
} catch (InterruptedException e) {
// shutdown()会在线程任务都执行完毕后将线程池关闭。
threadPool.shutdown();
}
}
运行结果:
pool-1-thread-1 线程创建一个新的SimpleDateFormat
pool-1-thread-3 线程创建一个新的SimpleDateFormat
pool-1-thread-4 线程创建一个新的SimpleDateFormat
pool-1-thread-2 线程创建一个新的SimpleDateFormat
=================================
pool-1-thread-1 线程复用上次使用过的SimpleDateFormat
pool-1-thread-3 线程复用上次使用过的SimpleDateFormat
pool-1-thread-4 线程复用上次使用过的SimpleDateFormat
pool-1-thread-1 线程复用上次使用过的SimpleDateFormat
=================================
pool-1-thread-3 线程复用上次使用过的SimpleDateFormat
pool-1-thread-2 线程复用上次使用过的SimpleDateFormat
pool-1-thread-4 线程复用上次使用过的SimpleDateFormat
pool-1-thread-3 线程复用上次使用过的SimpleDateFormat
所有线程任务执行完毕,回收localDateFormat。
线程池不用了。关闭线程池。如果之前没有对localDateFormat进行回收,线程池关闭后,线程里的localDateFormat会被回收。
如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat
推荐博文:DateTimeFormatter、LocalDateTime 的使用