文章目录
- 1. SimpleDateFormat介绍
- 2. 测试SimpleDateFormat的非线程安全性
- 3. 解决方案一
- 4. 解决方案二
 
1. SimpleDateFormat介绍
SimpleDateFormat是Java中的一个类,用于将日期对象格式化为特定的字符串表示形式,或者将特定格式的字符串解析为日期对象。它是java.text包中的一部分,提供了许多不同的模式(pattern)来定义日期和时间的格式。以下是SimpleDateFormat的使用详细介绍:
- 创建SimpleDateFormatd对象
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
其中,pattern是一个字符串,定义了日期和时间的格式。可以使用不同的模式字符来构建自定义的格式。例如,"yyyy-MM-dd"表示年份-月份-日期的格式。
- 格式化日期对象为字符串
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = new Date();
String formattedDate = sdf.format(date);
System.out.println(formattedDate);
- 解析字符串为日期对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateString = "2023-05-20";
Date date = sdf.parse(dateString);
System.out.println(date);
- 模式字符
SimpleDateFormat使用一些特定的模式字符来定义日期和时间的格式。以下是一些常用的模式字符:
y: 年份(例如:2023)
M: 月份(例如:05)
d: 日期(例如:20)
H: 小时(24小时制,例如:13)
h: 小时(12小时制,例如:01)
m: 分钟(例如:30)
s: 秒(例如:45)
S: 毫秒(例如:123)
可以根据需要组合这些模式字符来构建自定义的日期和时间格式。
- 完整使用案例
import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatExample {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        
        // 格式化日期对象为字符串
        Date date = new Date();
        String formattedDate = sdf.format(date);
        System.out.println("Formatted Date: " + formattedDate);
        
        // 解析字符串为日期对象
        String dateString = "2023-05-20";
        try {
            Date parsedDate = sdf.parse(dateString);
            System.out.println("Parsed Date: " + parsedDate);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 上面介绍完了SimpleDateFormat的基本使用方法,但它在多线程环境中使用容易造成数据转换及处理不准确,因为SimpleDateFormat并不是线程安全的。
2. 测试SimpleDateFormat的非线程安全性
public class Main{
    public static void main(String[] args) {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        String[] dateString=new String[]{
                "2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05",
                "2000-01-06", "2000-01-07", "2000-01-08", "2000-01-09", "2000-01-10"
        };
        MyThread[] threads=new MyThread[10];
        for (int i = 0; i < 10; i++) {
            threads[i]=new MyThread(sdf,dateString[i]);
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
    }
}
class  MyThread extends Thread{
   private  SimpleDateFormat simpleDateFormat;
   private  String dateString;
   public  MyThread(SimpleDateFormat simpleDateFormat,String dateString)
   {
       super();
       this.simpleDateFormat=simpleDateFormat;
       this.dateString=dateString;
   }
   @Override
    public void run(){
       try {
           //将字符床解析为date格式,然后有格式化为String格式
           Date dateRef=simpleDateFormat.parse(dateString);
           String newDateString=simpleDateFormat.format(dateRef).toString();
           if(!newDateString.equals(dateString))
           {
               System.out.println("ThreadName="+this.getName()+"错误,日期字符串为"+dateString+"转换成的日期"+newDateString);
           }
       }catch (ParseException e)
       {
           e.printStackTrace();
       }
   }
}

impleDateFormat是线程不安全的主要原因是它内部维护了一个Calendar对象来进行日期和时间的解析和格式化操作。由于Calendar对象本身是可变的,多个线程同时调用SimpleDateFormat的方法可能会导致竞争条件和数据不一致的问题。具体来说,SimpleDateFormat的parse()和format()方法在解析和格式化日期时会使用Calendar对象来进行计算和存储中间结果。然而,Calendar并不是一个线程安全的类。在多线程环境下,多个线程同时调用SimpleDateFormat的方法可能会共享同一个Calendar对象,导致并发访问的问题。例如,如果一个线程在解析日期时正在修改Calendar对象的状态,而另一个线程同时调用format()方法,那么就有可能出现数据不一致的情况。一个线程可能会看到另一个线程修改过的Calendar状态,导致解析或格式化结果出错。根据问题出现的原因,我们可以用下面方案进行解决:
3. 解决方案一
public class Main{
    public static void main(String[] args) {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        String[] dateString=new String[]{
                "2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05",
                "2000-01-06", "2000-01-07", "2000-01-08", "2000-01-09", "2000-01-10"
        };
        MyThread[] threads=new MyThread[10];
        for (int i = 0; i < 10; i++) {
            threads[i]=new MyThread(sdf,dateString[i]);
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
    }
}
class  MyThread extends Thread{
   private  SimpleDateFormat simpleDateFormat;
   private  String dateString;
   public  MyThread(SimpleDateFormat simpleDateFormat,String dateString)
   {
       super();
       this.simpleDateFormat=simpleDateFormat;
       this.dateString=dateString;
   }
   @Override
    public void run(){
       try {
           //使用DateTools类可以每次都创建一个新的SimpleDateFormat
              Date dateRef=DateTools.parse("yyyy-MM-dd",dateString);
              String newDateString=DateTools.format("yyyy-MM-dd",dateRef).toString();
           if(!newDateString.equals(dateString)){
               System.out.println("ThreadName="+this.getName()+"报错了,日期字符串"+dateString+"转换后的日期为"+newDateString);
           }
       }catch (ParseException e)
       {
           e.printStackTrace();
       }
   }
}
class  DateTools{
    public static Date parse(String formatPattern,String dateString) throws ParseException {
        return new SimpleDateFormat(formatPattern).parse(dateString);
    }
    public static String format(String formatPattern ,Date date){
        return  new SimpleDateFormat(formatPattern).format(date).toString();
    }
}
运行代码可以发现控制台什么都没输出,说明没有出现日期格式转换错误,该解决方法的原理就是创建了多个SimpleDateFormat类
4. 解决方案二
public class Main{
    public static void main(String[] args) {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        String[] dateString=new String[]{
                "2000-01-01", "2000-01-02", "2000-01-03", "2000-01-04", "2000-01-05",
                "2000-01-06", "2000-01-07", "2000-01-08", "2000-01-09", "2000-01-10"
        };
        MyThread[] threads=new MyThread[10];
        for (int i = 0; i < 10; i++) {
            threads[i]=new MyThread(sdf,dateString[i]);
        }
        for (int i = 0; i < 10; i++) {
            threads[i].start();
        }
    }
}
class  MyThread extends Thread{
   private  SimpleDateFormat simpleDateFormat;
   private  String dateString;
   public  MyThread(SimpleDateFormat simpleDateFormat,String dateString)
   {
       super();
       this.simpleDateFormat=simpleDateFormat;
       this.dateString=dateString;
   }
   @Override
    public void run(){
       try {
           //使用DateTools类可以每次都创建一个新的SimpleDateFormat
              Date dateRef=DateTools.getSimpleDateFormat("yyyy-MM-dd").parse(dateString);
              String newDateString=DateTools.getSimpleDateFormat("yyyy-MM-dd").format(dateRef).toString();
           if(!newDateString.equals(dateString)){
               System.out.println("ThreadName="+this.getName()+"报错了,日期字符串"+dateString+"转换后的日期为"+newDateString);
           }
       }catch (ParseException e)
       {
           e.printStackTrace();
       }
   }
}
class  DateTools{
      private static ThreadLocal<SimpleDateFormat> t1=new ThreadLocal<SimpleDateFormat>();
      public static SimpleDateFormat getSimpleDateFormat(String dateParttern){
          SimpleDateFormat sdf=null;
          sdf=t1.get();
          if(sdf==null){
              sdf=new SimpleDateFormat(dateParttern);
              t1.set(sdf);
          }
          return sdf;
      }
}
同样也成功的解决了SimpleDateFormat线程问题,这里使用的是ThreadLocal,它可以使线程绑定到指定的对象,使用该类可以解决多线程环境中类SimpleDateFormat处理日期时出现错误的问题



















