昨天知道了findbugs这个工具 而且用这个工具找到了潜在几个问题,有一个是便利map用的keyset findbugs建议改成entrtyset,还有一个就是 SimpleDateFormat不是线程安全的:
缺点:每调用一次方法就会创建一个SimpleDateFormat对象,方法结束又要作为垃圾回收。
缺点:性能较差,每次都要等待锁释放后其他线程才能进入
测试代码:
As the JavaDoc states, DateFormats are inherently unsafe for multithreaded use. The detector has found a call to an instance of DateFormat that has been obtained via a static field. This looks suspicous.
其实,出现这种问题的代码一般都长得差不多,典型的代码示例如下:
public class Test{
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
public void method1(){
dateFormat.format(new Date());
}
public void method2(){
dateFormat.format(new Date());
}
)
再给个详细例子说明问题,看下面代码:
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class SimpleDateFormatTest {
public static void main(String[] args) {
SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd");
Date today=new Date();
Date tomorrow=new Date(today.getTime()+1000*60*60*24);
System.out.println(today); // 今天是2010-01-11
System.out.println(tomorrow); // 明天是2010-01-11
Thread thread1=new Thread(new Thread1(dateFormat,today));
thread1.start();
Thread thread2 = new Thread(new Thread2(dateFormat,tomorrow));
thread2.start();
}
}
class Thread1 implements Runnable{
private SimpleDateFormat dateFormat;
private Date date;
public Thread1(SimpleDateFormat dateFormat,Date date) {
this.dateFormat = dateFormat;
this.date = date;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(;;){
String strDate=dateFormat.format(date);
if(!"2016-09-30".equals(strDate)){
System.err.println("today="+strDate);
System.exit(0);
}
}
}
}
class Thread2 implements Runnable{
private SimpleDateFormat dateFormat;
private Date date;
public Thread2(SimpleDateFormat dateFormat,Date date){
this.dateFormat = dateFormat;
this.date = date;
}
public void run() {
for(;;){
String strDate = dateFormat.format(date);
if(!"2016-10-01".equals(strDate)){
System.err.println("tomorrow="+strDate);
System.exit(0);
}
}
}
}
运行的结果如下:
Fri Sep 30 11:06:49 CST 2016
Sat Oct 01 11:06:49 CST 2016
today=2016-10-01
错得很明显了。
解决方案:
1. 解决方案a:
将SimpleDateFormat定义成局部变量:
- SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
- String str1 = "01-Jan-2010";
- String str2 = sdf.format(sdf.parse(str1));
2. 解决方案b:
加一把线程同步锁:synchronized(lock)
- public class SyncDateFormatTest {
- private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
- private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };
- public static void main(String[] args) {
- for (int i = 0; i < date.length; i++) {
- final int temp = i;
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- while (true) {
- synchronized (sdf) {
- String str1 = date[temp];
- Date date = sdf.parse(str1);
- String str2 = sdf.format(date);
- System.out.println(Thread.currentThread().getName() + ", " + str1 + "," + str2);
- if(!str1.equals(str2)){
- throw new RuntimeException(Thread.currentThread().getName()
- + ", Expected " + str1 + " but got " + str2);
- }
- }
- }
- } catch (Exception e) {
- throw new RuntimeException("parse failed", e);
- }
- }
- }).start();
- }
- }
- }
3. 解决方案c: (推荐)
使用ThreadLocal: 每个线程都将拥有自己的SimpleDateFormat对象副本。
写一个工具类:
- public class DateUtil {
- private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>();
- public static Date parse(String str) throws Exception {
- SimpleDateFormat sdf = local.get();
- if (sdf == null) {
- sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
- local.set(sdf);
- }
- return sdf.parse(str);
- }
- public static String format(Date date) throws Exception {
- SimpleDateFormat sdf = local.get();
- if (sdf == null) {
- sdf = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
- local.set(sdf);
- }
- return sdf.format(date);
- }
- }
- public class ThreadLocalDateFormatTest {
- private static String date[] = { "01-Jan-1999", "01-Jan-2000", "01-Jan-2001" };
- public static void main(String[] args) {
- for (int i = 0; i < date.length; i++) {
- final int temp = i;
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- while (true) {
- String str1 = date[temp];
- Date date = DateUtil.parse(str1);
- String str2 = DateUtil.format(date);
- System.out.println(str1 + "," + str2);
- if(!str1.equals(str2)){
- throw new RuntimeException(Thread.currentThread().getName()
- + ", Expected " + str1 + " but got " + str2);
- }
- }
- } catch (Exception e) {
- throw new RuntimeException("parse failed", e);
- }
- }
- }).start();
- }
- }
- }