Java日期时间API系列4—–Jdk7及以前的日期时间类的线程安全问题

1.Date类为可变的,在多线程并发环境中会有线程安全问题。

(1)可以使用锁来处理并发问题。

(2)使用JDK8  Instant 或 LocalDateTime替代。

2.Calendar的子类为可变的,在多线程并发环境中会有线程安全问题。

(1)可以使用锁来处理并发问题。

(2)使用JDK8  LocalDateTime 替代。

3.DateFormat和SimpleDateFormat不是线程安全的原因

(1)DateFormat中calendar是共享变量,其子类SimpleDateFormat中也是共享变量。

DateFormat源码:

public abstract class DateFormat extends Format {

/**
* The {@link Calendar} instance used for calculating the date-time fields
* and the instant of time. This field is used for both formatting and
* parsing.
*
*

Subclasses should initialize this field to a {@link Calendar}
* appropriate for the {@link Locale} associated with this
* DateFormat.
* @serial
*/
protected Calendar calendar;

(2)SimpleDateFormat format方法源码:

    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

        boolean useDateFormatSymbols = useDateFormatSymbols();

        for (int i = 0; i < compiledPattern.length; ) {
            int tag = compiledPattern[i] >>> 8;
            int count = compiledPattern[i++] & 0xff;
            if (count == 255) {
                count = compiledPattern[i++] << 16;
                count |= compiledPattern[i++];
            }

            switch (tag) {
            case TAG_QUOTE_ASCII_CHAR:
                toAppendTo.append((char)count);
                break;

            case TAG_QUOTE_CHARS:
                toAppendTo.append(compiledPattern, i, count);
                i += count;
                break;

            default:
                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                break;
            }
        }
        return toAppendTo;
    }

  当多个线程同时使用相同的 SimpleDateFormat 对象【如用static修饰的 SimpleDateFormat 】调用format方法时,多个线程会同时调用 calendar.setTime 方法,可能一个线程刚设置好 time 值另外的一个线程马上把设置的 time 值给修改了导致返回的格式化时间可能是错误的。

4.SimpleDateFormat线程安全使用。

(1)使用ThreadLocal处理static方法

    public static final ThreadLocal df = new ThreadLocal() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    };
System.out.println(df.get().format(new Date()));
2019-12-14

(2)使用JDK8  DateTimeFormatter 替代。

 

参考:https://www.cnblogs.com/wupeixuan/p/11511915.html?utm_source=gold_browser_extension

  《阿里巴巴Java开发手册》

Java日期时间API系列3—–Jdk7及以前的日期时间类的不方便使用问题

使用Java日期时间类,每个人都很熟悉每个项目中必不可少的工具类就是dateutil,包含各种日期计算,格式化等处理,而且常常会遇到找不到可用的处理方法,需要自己新增方法,处理过程很复杂。

1.Date中的过时方法等

Date中的方法一般都过时了,不建议使用,有一些歧义。比如:

(1)new Date(2019,01,01)实际是3919年2月。因为Date的构造函数 的年份表示的始于1900年的差值。

(2)month是从0开始的。

(3)Date如果不格式化,打印出的日期可读性差。

Fri Dec 13 23:08:12 CST 2019

(4)Date和java.sql.Date命名完全一样,不易区分。

2 Calendar操作繁琐、不支持复杂计算等

Calendar虽然能够处理大部分的Date计算,但设计不是很成功,一些简单操作都要多次调用。对一些复杂的计算比如两个日期之间有多少个月,生日计算年龄等都不支持。比如:

(1)DAY_OF_WEEK 的取值,是从周日(1)开始的。

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        System.out.println(calendar.get(Calendar.DAY_OF_WEEK));

(2)MONTH 的取值,是从0开始的。

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        System.out.println(calendar.get(Calendar.MONTH));

(3)set()方法延迟修改

通过set()方法设置某一个字段的值得时候,该字段的值不会立马修改,直到下次调用get()、getTime()等时才会重新计算日历的时间。延迟修改的优势是多次调用set()方法不会触发多次不必要的计算。下面程序演示了set()方法延迟修改的效果:

Calendar cal = Calendar.getInstance();
cal.set(2003,7,31);//2003-8-31
//将月份设为9,但9月31不存在
//如果立即修改,系统会把cal自动调整到10月1日
cal.set(Calendar.MONTH,8);
//下面代码输出了10月1日
System.out.println(cal.getTime());//(1)
//设置DATE字段为5
cal.set(Calendar.DATE, 5);//(2)
System.out.println(cal.getTime());//(3)

打印结果为:

Wed Oct 01 22:25:41 CST 2003
Sun Oct 05 22:25:41 CST 2003

如果将(1)处的代码注释掉,打印结果为:

Fri Sep 05 22:28:06 CST 2003

你看明白了吗?如果将(1)处的代码注释掉,因为set()方法具有延迟性,它内部只是先记录下MONTH字段的值为8,接着程序将DATE字段设置为5,程序内部再次记录DATE字段的值为5——就是9月5日。

3.日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

 

参考:https://www.jianshu.com/p/1478af429a1e

      https://www.cnblogs.com/bingyimeiling/p/10420752.html

Java日期时间API系列2—–Jdk7及以前的日期时间类在mysql数据库中的应用

1.java中与数据库相关的时间类

java提供与mysql方便交互的三种数据类型:

java.sql.Date

java.sql.Time

java.sql.Timestamp

它们都是继承java.util.Date,算是对该类的精简,很适合跟数据库交互。

2.MySQL 中的日期和时间类型

日期和时间类型 字节 最小值 最大值
DATE 4 1000-01-01 9999-12-31
DATETIME 8 1000-01-01 00:00:00 9999-12-31 23:59:59
TIMESTAMP 4 19700101080001 2038 年的某个时刻
TIME 3 -838:59:59 838:59:59
YEAR 1 1901 2155

 

3.java中类与mysql中类型的匹配

java类型 mysql类型
timestamp  datetime
timestamp  timestamp
timestamp  date 保留日期部分
timestamp  time 保留时间部分
date date
time time

 

4.mysql中datetime与timestamp的区别

a)DATETIME的默认值为null,5.6版本之后,也能用CURRENT_TIMESTAMP;TIMESTAMP的字段默认不为空(not null),默认值为当前时间(CURRENT_TIMESTAMP),如果不做特殊处理,并且update语句中没有指定该列的更新值,则默认更新为当前时间。

b)DATETIME使用8字节的存储空间,TIMESTAMP的存储空间为4字节。因此,TIMESTAMP比DATETIME的空间利用率更高。

c)两者的存储方式不一样 ,对于TIMESTAMP,它把客户端插入的时间从当前时区转化为UTC(世界标准时间)进行存储。查询时,将其又转化为客户端当前时区进行返回。而对于DATETIME,不做任何改变,基本上是原样输入和输出。

d)两者所能存储的时间范围不一样:

timestamp所能存储的时间范围为:’1970-01-01 00:00:01.000000’ 到 ‘2038-01-19 03:14:07.999999’;

datetime所能存储的时间范围为:’1000-01-01 00:00:00.000000’ 到 ‘9999-12-31 23:59:59.999999’。

所以,如果要适用广泛的,建议适用DATETIME。

参考:https://www.jianshu.com/p/937656cb269b

      https://blog.csdn.net/z3278221/article/details/81000876

 

 

 

Java日期时间API系列1—–Jdk7及以前的日期时间类

先看一个简单的图:

 

 

主要的类有:

Date类负责时间的表示,在计算机中,时间的表示是一个较大的概念,现有的系统基本都是利用从1970.1.1 00:00:00 到当前时间的毫秒数进行计时,这个时间称为epoch。在后文中如果没有明确说明,毫秒数就是指从1970年到对应时间的毫秒数。在Java 的Date类内部其实也是一个毫秒数,对外表现为一个Date对象。

Calendar是一个工具类,负责对Date类进行修改等操作,以及从Date类中提取年月日等时间的特定信息。

DateFormat 则负责日期的转换,比如读取特定格式的字符串,转换成date对象,或者将date对象按照指定的格式转成字符串。

1 Date 表示时刻。内部主要是一个long值存储距离纪元时的毫秒数。绝大多数方法都是过时的。

这里指的是:java.util.Date 日期格式:年月日时分秒

    public class Date{
        private transient long fastTime;
        Date(){
            this(System.currentTimeMillis());
        }
        Date(long date){
            fastTime = date;
        }
        //……
    }

2 TimeZone 时区(24个,如Asia/shanghai)

//获取默认时区
TimeZone.getDefault();

3 Locale 国家(或地区)和语言(如zh_CN)

// 获取默认国家和语言
Locale.getDefault();

4 Calendar 是日期和时间操作的主要类,是抽象类,提供了多个静态方法,可以获取Calendar实例。

与Date类似,Calendar内部也有一个表示时刻的毫秒数,还定义了一个数组(长度17),表示日历中各个字段的值。

proteted long time;
proteted int fields[];

fields中存放的是下面这些字段的值,给Calendar

    Calendar.YEAR,
    Calendar.MONTH,
    Calendar.DAY_OF_MONTH,
    Calendar.DAY_OF_WEEK,
    Calendar.HOUR_OF_DAY,
    Calendar.MINUTE,
    Calendar.SECOND,
    Calendar.MILLISECOND

可以通过Calendar实例获取这些值(Calendar会根据时区,地区语言进行转换)。

    //空构造函数,会获取当前的。 Calendar.getInstance("Asia/shanghai","zh_CN")
    Calendar calendar = Calendar.getInstance();
    int day = calendar.get(Calendar.DAY_OF_MONTH);

Calendar还支持根据字段增加减少时间(负数表示减少)。

    Calendar.getInstance().add(Calendar.MONTH,-2);

总结来说,Calendar做了一项非常繁琐的工作,根据TimeZone 和 Locale 再绝对时间毫秒数和日历字段之间自动进行转换。

5 DateFormat(线程不安全) 提供Date和字符串表示之间的转换,主要的两个方法 format(Date d),parse(String s)

Date的字符串表示与TimeZone、Locale都是相关的。
同时与两个格式化风格相关,一个是日期格式化风格,一个是时间格式化风格。
DataFormat是抽象方法,也用工厂方法创建对象。

    DateFormat.getTimeInstance();
    DateFormat.getDateInstance();
    DateFormat.getDateTimeInstance();

其中getTimeInstance只处理时间,getDateInstance只处理日期,getDateTimeInstance处理日期和时间,三种对象的处理结果如下

    Calendar calendar = Calendar.getInstance();
    
    //结果是21:34:20
    DateFormat.getTimeInstance().format(calendar.getTime());
    //结果是2019-02-20
    DateFormat.getDateInstance().format(calendar.getTime());
    //结果是2019-02-20 21:34:20
    DateFormat.getDateTimeInstance().format(calendar.getTime());

DateFormat虽然比较方便,但是日期字符串格式的更精确的控制,则应该使用SimpleDateFormat。

6 SimpleDateFormat(线程不安全) 是DateFormat的子类,与父类主要不同是:子类可以自定义日期格式。

    String pattern = "yyyy年MM月dd日 E HH时mm分ss秒";
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
    //输出结果是 2019年02月20日 Wed 21时44分42秒
    OS.print(simpleDateFormat.format(calendar.getTime()));

pattern 中的英文字符a-z,A-Z表示特殊含义,其他字符原样输出.

pattern变量参数 对应的实际意义 示例
yyyy 4位的年份 2019
MM 月份两位数 02
dd 日期 20
HH/hh 24小时制/12小时制 21/09
mm 分钟 55
ss 55
E 星期几 wed
a 上午下午,一般配合hh使用 PM

SimpleDateFormat也可以方便的将字符串转成Date。

    String str = "19年2月20日 09时58分33秒111";
    Date date = new SimpleDateFormat("yy年M月dd日 HH时mm分ss秒SSS").parse(str);
    //结果 2019年02月20日 09时58分33秒
    String result = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒").format(date);

7.java.sql.Date继承于java.util.Date,只保留了日期

java.sql.Date类在JDBC API中被使用,日期格式:年月日。如果你需要在java.sql.PreparedStatement上设置日期或者从java.sql.ResultSet获取日期,你将会和java.sql.Date打交道。
你可以用java.util.Date做的任何事同样适用于java.sql.Date。

long time = System.currentTimeMillis();
java.sql.Date date = new java.sql.Date(time);

java.sql.Datejava.util.Date最大的不同在于java.sql.Date所表示的日期中只保留了日期,而没有时间。

举个例子,如果你用2009-12-24 23:20来创建一个java.sql.Date,那么其中的时间(23:20)将会被切掉。如果你需要保留时间,使用 java.sql.Timestamp 来代替java.sql.Date

8.java.sql.Time继承于java.util.Date,只保留了时间

同java.sql.Date类在JDBC API中被使用,日期格式:时分秒。

9.java.sql.Timestamp继承于java.util.Date,对java.util.Date 类进行了扩充,该类提供了 getNanos() 方法

同java.sql.Date类在JDBC API中被使用,日期格式:年月日时分秒纳秒。

10.TimeUnit是一个时间单位枚举类,主要用于并发编程

时间单元表示给定粒度单元的时间持续时间,并提供实用程序方法来跨单元转换,以及在这些单元中执行计时和延迟操作。时间单元不维护时间信息,但只帮助组织和使用可能在不同上下文中分别维护的时间表示。

例如:尝试获取锁50毫秒:

  Lock lock = ...;
   if (lock.tryLock(50L, TimeUnit.MILLISECONDS)) ...

 

参考:https://www.jianshu.com/p/1478af429a1e

     https://blog.csdn.net/zhao123h/article/details/53012791

     http://ifeve.com/java-sql-date/