ThreadLocal的使用
除了控制资源的访问外,我们可以增加资源来保证所有对象的线程安全。比如,让100个人填写个人信息表,如果只有一支笔,那么大家就得挨个填写,对于管理人员来说,必须保证大家不会去哄抢这仅存的一支笔,否则谁也填不完。从另外一个角度出发,我们可以干脆准备100支笔,人手一支,那么所有人都可以各自为营,很快就能完成表格的填写。
如果说锁是使用第一种思路,那么ThreadLocal就是使用第二种思路了。
从ThreadLocal的名字上可以看到,这是一个线程的局部变量,也就是说,只有当前线程可以访问。既然是只有当前线程可以访问的数据,自然就是线程安全的。
privatestatic final SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-ddHH:mm:ss");
public static class ParseDate implementsRunnable{
inti=0;
publicParseDate(int i){
this.i=i;
}
publicvoid run(){
try{
Datet=sdf.parse("2017-01-01 12:00"+i%60);
System.out.println(i+":"+t);
}catch (ParseException e) {
//TODO: handle exception
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorServicees=Executors.newFixedThreadPool(10);
for(int i = 0; i < 1000; i++) {
es.execute(newParseDate[i]);
}
}
}
上诉代码在多线程中使用SimpleDateFormat来解析字符串类型的日期。如果你执行上述代码,一般来说,很可能得到一些异常:
出现这些问题的原因,是SimpleDateFormat.parse()方法并不是线程安全的。因此,在线程池中共享这个对象必然导致错误。
一种可行的方案是在sdf.parse()前后加锁,这也是我们一般的处理思路。这里使用ThreadLocal为每一个线程都产生一个SimpleDateFormat对象
static ThreadLocal<SimpleDateFormat> t1=newThreadLocal<SimpleDateFormat>();
public static class ParseDate implementsRunnable{
inti=0;
publicParseDate(int i){
this.i=i;
}
publicvoid run(){
try{
if(t1.get()==null){
t1.set(newSimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
}
Datet=sdf.parse("2017-01-01 12:00"+i%60);
System.out.println(i+":"+t);
}catch (ParseException e) {
//TODO: handle exception
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorServicees=Executors.newFixedThreadPool(10);
for(int i = 0; i < 1000; i++) {
es.execute(newParseDate[i]);
}
}
}
如果当前线程不持有SimpleDateFormat对象实例,那么就新建一个,并把它设置到当前线程中,如果已经持有,则直接使用。
从这里看出,为每一个线程分配一个对象的工作并不是由ThreadLocal来完成的,而是需要在应用层面保证。如果在应用上为每一个线程分配了相同的对象实例,那么ThreadLocal页不能保证线程安全。