SimpleDateFormat、FastDateFormat和Joda-Time

注意:SimpleDateFormat是线程不安全的,在多线程情况下会出现线程安全问题。而FastDateFormat和Joda-Time都是线程安全的,可以放心使用。例如:SimpleDateFormat在对时间进行格式化的方法format中,会先对calendar对象进行setTime的赋值,若是有多个线程同时操作一个SimpleDateFormat实例的话,就会对calendar的赋值进行覆盖,进而产生问题。

SimpleDateFormat是JDK提供的,不需要依赖第三方jar包,而其他两种都得依赖第三方jar包。

FastDateFormat是apache的commons-lang3包提供的

Joda-Time需要依赖以下maven的配置(现在最新版本就是2.9.4)

<!--?xml version="1.0" encoding="UTF-8" standalone="no"?--> <dependency>
  <groupId>joda-time</groupId>
  <artifactId>joda-time</artifactId>
  <version>2.9.4</version>
</dependency>

如何避免simpleDateFormat出现线程不安全问题?

1.、避免使用成员变量,在每次需要使用的时候,进行SimpleDateFormat实例的创建,这种方式会导致创建一些对象实例,占用一些内存,不建议这样使用。

2、加锁,使用同步的方式,在调用方法的时候加上synchronized,这样可以让线程调用方法时,进行加锁,也就是会造成线程间的互斥,对性能影响比较大。

3、使用ThreadLocal进行保存,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。相当于一个线程只会有一个实例,进而减少了实例数量,也防止了线程间的互斥,推荐使用这种方式。

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class DateUtil {

/**
* 锁对象
*/
private static final Object lockObj = new Object(); 

/**
* 存放不同的日期模板格式的sdf的Map
*/
private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();


/**
* 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
* 
* @param pattern
* @return
*/
private static SimpleDateFormat getSdf(final String pattern) {
    ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);

    // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
    if (tl == null) {
        synchronized (lockObj) {
            tl = sdfMap.get(pattern);
            if (tl == null) {

                // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
                tl = new ThreadLocal<SimpleDateFormat>() {

                @Override
                protected SimpleDateFormat initialValue() {
        
                    return new SimpleDateFormat(pattern);
                }
            };
            sdfMap.put(pattern, tl);
        }
    }
}

return tl.get();
}

/**
* 使用ThreadLocal<SimpleDateFormat>来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
* 如果新的线程中没有SimpleDateFormat,才会new一个
* @param date
* @param pattern
* @return
*/
public static String format(Date date, String pattern) {
    return getSdf(pattern).format(date);
}

public static Date parse(String dateStr, String pattern) throws ParseException {
    return getSdf(pattern).parse(dateStr);
}
}


FastDateFormat是线程安全的,可以直接使用,不必考虑多线程的情况。

FastDateFormat format = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss");  
System.out.println(format.format(new Date()));  
  
// 可以使用DateFormatUtils类来操作,方法里面也是使用的FastDateFormat类来做的  
System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss"));  

Joda-Time与以上两种有所区别如下:

1.不仅仅可以对时间进行格式化输出,而且可以生成瞬时时间值,并与Calendar、Date等对象相互转化,极大的方便了程序的兼容性。

2.Joda-Time的类具有不可变性,因此他们的实例是无法修改的,就跟String的对象一样。这种不可变性提现在所有API方法中,这些方法返回的都是新的类实例,与原来实例不同。

// 得到当前时间  
Date currentDate = new Date();  
DateTime dateTime = new DateTime();  // DateTime.now()  
  
System.out.println(currentDate.getTime());  
System.out.println(dateTime.getMillis());  
  
// 指定某一个时间,如2016-08-29 15:57:02  
Date oneDate = new Date(1472457422728L);  
DateTime oneDateTime = new DateTime(1472457422728L);  
DateTime oneDateTime1 = new DateTime(2016, 8, 29, 15, 57, 2, 728);  
  
System.out.println(oneDate.toString());  
System.out.println(oneDateTime.toString());  // datetime默认的输出格式为yyyy-MM-ddTHH:mm:ss.SSS  
System.out.println(oneDateTime1.toString("MM/dd/yyyy hh:mm:ss.SSSa"));  // 直接就可以输出规定的格式  
  
// DateTime和Date之间的转换  
Date convertDate = new Date();  
DateTime dt1 = new DateTime(convertDate);  
System.out.println(dt1.toString());  
  
Date d1 = dt1.toDate();  
System.out.println(d1.toString());  
  
// DateTime和Calendar之间的转换  
Calendar c1 = Calendar.getInstance();  
DateTime dt2 = new DateTime(c1);  
System.out.println(dt2.toString());  
  
Calendar c2 = dt2.toCalendar(null);  // 默认时区Asia/Shanghai  
System.out.println(c2.getTimeZone());  
  
// 时间格式化  
DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");  
DateTime dt3 = DateTime.parse("2016-08-29 13:32:33", formatter);  
System.out.println(dt3.toString());  
// 若是不指定格式,会采用默认的格式,yyyy-MM-ddTHH:mm:ss.SSS,若被解析字符串只到年月日,后面的时分秒会全部默认为0  
DateTime dt4 = DateTime.parse("2016-08-29T");  
System.out.println(dt4.toString());  
// 输出locale 输出2016年08月29日 16:43:14 星期一  
System.out.println(new DateTime().toString("yyyy年MM月dd日 HH:mm:ss EE", Locale.CHINESE));  
  
// 计算两个日期间隔的天数  
LocalDate start = new DateTime().toLocalDate();  
LocalDate end = new LocalDate(2016, 8, 25);  
System.out.println(Days.daysBetween(start ,end).getDays()); // 这里要求start必须早于end,否则计算出来的是个负数  
// 相同的还有间隔年数、月数、小时数、分钟数、秒数等计算  
// 类如Years、Hours等  
  
// 对日期的加减操作  
DateTime dt5 = new DateTime();  
dt5 = dt5.plusYears(1)          // 增加年  
        .plusMonths(1)          // 增加月  
        .plusDays(1)            // 增加日  
        .minusHours(1)          // 减小时  
        .minusMinutes(1)        // 减分钟  
        .minusSeconds(1);       // 减秒数  
System.out.println(dt5.toString());  
  
// 判断是否闰月  
DateTime dt6 = new DateTime();  
DateTime.Property month = dt6.monthOfYear();  
System.out.println(month.isLeap());  

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值