第一版
首先看一个SimpleDateFormat的例子
public static void main(String[] args) {
new Thread(()->{
String date=new ThreadLocalDemo2().formatDate(1);
System.out.println(date);
}).start();
new Thread(()->{
String date=new ThreadLocalDemo2().formatDate(2);
System.out.println(date);
}).start();
}
public String formatDate(int sec){
Date date=new Date(sec*1000);
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("mm:ss");
return simpleDateFormat.format(date);
}
上面的例子显示的是开辟两个线程,输出当前时间,由于都是新建SimpleDateFormat对象,而且线程数比较少,并没有什么竞争,所以答案就是00:01和00:02,并没有冲突发生。
再升级
接着进化一下,用线程池开辟16个线程,然后打印20次
public class ThreadLocalDemo3 implements Runnable {
public static void main(String[] args) {
ExecutorService executorService= Executors.newFixedThreadPool(16);
for (int i = 0; i <20 ; i++) {
final int finalI=i;
executorService.submit(()->{
String date=new ThreadLocalDemo3().formatDate(finalI);
System.out.println(date);
});
}
}
@Override
public void run() {
}
public String formatDate(int sec){
Date date=new Date(sec*1000);
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("mm:ss");
return simpleDateFormat.format(date);
}
}
可以看到如下结果
00:05
00:04
00:09
00:02
00:18
00:19
00:17
00:16
00:01
00:08
00:06
00:00
00:10
00:07
00:03
00:11
00:12
00:13
00:14
00:15
这样也是可行的,并没有发生重复的数据,线程是安全的。
再进一步去实现一下
当每次调用都会产生一个新的对象的时候,势必对内存造成更多的占用,现在将SimpleDateFormat实现资源共用。
由于产生了资源共用,则触发了线程不安全的问题,所以导致了重复的结果出现。
尝试解决
可以对SimpleDateFormat进行加锁,使用synchronized修饰
public String formatDate(int sec) {
String s=null;
synchronized (ThreadLocalDemo3.class) {
Date date = new Date(sec * 1000);
s= simpleDateFormat.format(date);
}
return s;
}
既然加了锁,当然线程变成安全的了。既然加了锁,那就变成了一种排队的情况,这样处理就会大大降低,所以,可以引入ThreadLocal进行解决。
public class ThreadLocalDemo3 {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
static ExecutorService executorService = Executors.newFixedThreadPool(8);
public static void main(String[] args) {
Set<String> dateSet = new HashSet<>();
for (int i = 0; i < 1000; i++) {
final int finalI = i;
executorService.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalDemo3().getDate(finalI);
System.out.println(date);
if (dateSet.contains(date)) {
throw new RuntimeException("该date已存在");
} else {
dateSet.add(date);
}
}
});
}
executorService.shutdown();
}
public String formatDate(int sec) {
String s = null;
synchronized (ThreadLocalDemo3.class) {
Date date = new Date(sec * 1000);
s = simpleDateFormat.format(date);
}
return s;
}
public String getDate(Integer sec) {
return getThreadLocalSimpleDateFormat().format(new Date(sec*1000));
}
public SimpleDateFormat getThreadLocalSimpleDateFormat() {
ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("mm:ss");
}
};
return threadLocal.get();
}
}
原理是利用ThreadLocal线程进行初始化对象,即每个线程都会创建个SimpleDateFormat对象,对象数并不会太多,有多少线程就会有多少对象,也就是说线程池中有16个线程协助运转,那么就有16个对象。
在ThreadLocal中,线程内的资源是共享的,但是兄弟线程是不共线资源的。
为了防止内存泄漏,使用ThreadLocal后要对其对象进行remove操作