1. 场景
在项目中经常用得到 SimpleDateFormat时间转化类,但是其并非线程安全的。可通过一个实例代码来说明。
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DateFormatExample1 {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
update();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
}
private static void update() {
try {
simpleDateFormat.parse("20180208");
} catch (Exception e) {
log.error("parse exception", e);
}
}
}
2. 分析使用到的类与方法是否线程安全。
- 发布的对象是可变类且线程间共享
- 多个线程可以同时修改该对象
分析sdf的源码可知SimpleDateFormat 类内部有一个 Calendar 对象引用,它用来储存和这个 SimpleDateFormat 相关的日期信息,例如sdf.parse(dateStr),sdf.format(date) 诸如此类的方法参数传入的日期相关String, Date等等。而这个发布的对象又可以被多个线程同时修改,进而导致了时间混乱的问题。
3. 线程安全策略
通过线程安全策略的一般方法来研究这个问题的解决办法,
---> 首先,无法将Calendar对象定义成不可变对象。
---> 其次,我们来看看线程封闭的策略,如果将sdf对象封闭在线程里,也就是每个线程创建一个 sdf对象,这样虽然避免了线程安全的问题,但是存在大量的资源消耗。这样我们再看 ThreadLocal的封闭策略,这种方式确实不错,我们通过控制最多的并发线程数可以限制实例化的sdf对象,并且封闭在各自的线程之中。
---> 同步容器,这里可以使用第三方 Joda-Time包
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
@Slf4j
public class DateFormatExample3 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(() -> {
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
}
private static void update(int i) {
log.info("{}, {}", i, DateTime.parse("20180208", dateTimeFormatter).toDate());
}
}
4. ThreadLocal 使用要注意的问题
ThreadLocal
ThreadLocal
ThreadLocal
在线程池环境下,由于线程是一直运行且复用的,使用ThreadLocal
5. DateTimeFormatter (Java 8支持)
更新下,Java 8内置的DateTimeFormatter是线程安全的,放心使用。
https://www.liaoxuefeng.com/wiki/1252599548343744/1303985694703650
参考链接:
https://blog.csdn.net/zq602316498/article/details/40263083
https://www.cnblogs.com/zhuimengdeyuanyuan/archive/2017/10/25/7728009.html
Java面试必问,ThreadLocal终极篇