java基础-时间相关(篇一 java7)

2020-03-11  本文已影响0人  Mark_Du

简单介绍一下java时间相关的操作,以及线程并发相关的一些问题

首先,java时间相关类,常见的情况,分java7和java8两个版本来讨论。

  1. java7相关的时间操作
@Test
    public void java7DateTest(){

        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //日期
        Date myDate = new Date();
        System.out.println(myDate.toString());
        //output Fri Nov 22 09:22:37 CST 2019
        System.out.println(simpleDateFormat.format(myDate));
        //output 2019-11-22 09:23:48

        //字符串转时间戳
        String str = "2019-11-20 11:08:00";
        try{
            Date date = simpleDateFormat.parse(str);
            long ts = date.getTime();
            System.out.println(ts);
        }catch(Exception e){
            e.getMessage();
            System.out.println("here");
        }
        //output 1574219280000


        //时间戳转字符串
        long timestamp = 1574737744449L;
        String timeStr = simpleDateFormat.format(timestamp);
        System.out.println("current Beijing time "+timeStr);
        //output current Beijing time 2019-11-26 11:09:04

        //与时区相关
        System.out.println(TimeZone.getDefault());
        //output sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=19,lastRule=null]
        System.out.println(System.getProperty("user.timezone"));
        //output Asia/Shanghai

        simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/Chicago"));
        System.out.println("current Chicago time :"+simpleDateFormat.format(timestamp));
        //out put current Chicago time :2019-11-25 21:09:04
    }

代码这里使用了单元测试的方式来写的,不会单元测试的同学,自己加一个main方法来调用也可以。
时间相关的操作,做的最多就是:时间戳和年月日字符串相互转换
常见的就是,存入数据库的时候存入时间戳,提取出来给页面的时候,需要给年月日字符串。
这里就分化出一些操作,比如,根据时区进行转换,或者提取单独的年,月,日等。
整个转换过程中,最核心的类就是 SimpleDateFormat
这个SimpleDateFormat存在一点问题,就是 线程不安全
首先解释下 线程不安全
大致意思就是,在多线程环境下使用存在一定风险
先看下面这个例子

package com.duanmin.redisdemo;

public class ThreadSafeDemo implements Runnable {
    public static int count=1;
    public void run() {
        while(count<10) {
            System.out.println(Thread.currentThread().getName()+"-执行前count="+count);
            try {
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-执行后count="+count);
        }

    }
    public static void main(String[] args) {
        ThreadSafeDemo Thread1=new ThreadSafeDemo();
        Thread mThread1=new Thread(Thread1,"线程1");
        Thread mThread2=new Thread(Thread1,"线程2");
        Thread mThread3=new Thread(Thread1,"线程3");
        mThread1.start();
        mThread2.start();
        mThread3.start();
    }
}

这是一个非常简单的例子,就是对于i++类型的多线程调用
执行一下,其中一次的结果是这样的

线程1-执行前count=1
线程3-执行前count=1
线程3-执行后count=3
线程2-执行前count=1
线程3-执行前count=3
线程1-执行后count=2
线程1-执行前count=5
线程1-执行后count=6
线程3-执行后count=5
线程2-执行后count=4
线程3-执行前count=6
线程3-执行后count=7
线程1-执行前count=6
线程3-执行前count=7
线程3-执行后count=9
线程3-执行前count=9
线程2-执行前count=6
线程3-执行后count=10
线程1-执行后count=8
线程2-执行后count=11

执行结果每次都不一样

然后,我们只启动线程1

package com.duanmin.redisdemo;

public class ThreadSafeDemo implements Runnable {
    public static int count=1;
    public void run() {
        while(count<10) {
            System.out.println(Thread.currentThread().getName()+"-执行前count="+count);
            try {
                count++;
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-执行后count="+count);
        }

    }
    public static void main(String[] args) {
        ThreadSafeDemo Thread1=new ThreadSafeDemo();
        Thread mThread1=new Thread(Thread1,"线程1");
        Thread mThread2=new Thread(Thread1,"线程2");
        Thread mThread3=new Thread(Thread1,"线程3");
        mThread1.start();
       // mThread2.start();
       // mThread3.start();
    }
}

得到的结果非常稳定

线程1-执行前count=1
线程1-执行后count=2
线程1-执行前count=2
线程1-执行后count=3
线程1-执行前count=3
线程1-执行后count=4
线程1-执行前count=4
线程1-执行后count=5
线程1-执行前count=5
线程1-执行后count=6
线程1-执行前count=6
线程1-执行后count=7
线程1-执行前count=7
线程1-执行后count=8
线程1-执行前count=8
线程1-执行后count=9
线程1-执行前count=9
线程1-执行后count=10

多线程执行过长中count的值是很乱的,而且最后出现了一个11

整个代码执行的过程,可以分解一下

1.比较count的值
2.对当前count值加1

这两个步骤,在这段代码里,是非原子

原子性这个概念,表明一系列操作,一系列执行动作,像原子一样,不可分割,一系列的操作,要么全执行,要么一个都不执行,没有中间状态

放到多线程环境下,非原子的多步骤操作,就会出现线程安全的问题

在我们这种常规的应用程序中,在多线程的并发环境下,如果没有使用额外的同步手段来处理并发问题,那么,线程的调度,是不可控,不可预期的,谁先执行,谁后执行,是不确定的

最后出现11的情况可能是这样的:

1. 线程2拿到count值为9,小于10
   同时线程3也拿到count值9
2. 线程3先执行自增,这时count值为10
3. 线程2执行自增,这时count值已经是10了,加1之后变成11

好了,线程安全简单的理解了,对于java7版本的时间类来说, SimpleDateFormat 存在线程安全问题,从源码可以看出。
跟着format往下看源码,可以找到一段

// Called from Format after creating a FieldDelegate
    private StringBuffer format(Date date, StringBuffer toAppendTo,
                                FieldDelegate delegate) {
        // Convert input date to time field list
        calendar.setTime(date);

这里用的 calendar 是这样定义的:

protected Calendar calendar;

如果在一段代码中,我们声明了一个对象:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat()

然后把它放到多线程环境下使用,就会出现上面我们demo里面的情况
calendar.setTime(date);
被交叉调用,得到的结果不是自己想要的。

解决办法有几种

  1. 在每次使用的地方new一个SimpleDateFormat,不重复使用即可
    当然,这也会带来性能的损耗,在一些大型性能,每个地方的损耗都会考虑到。
  2. 在使用的地方加锁,这个会消耗性能。
  3. 使用 ThreadLocal,每个线程用自己的SimpleDateFormat
  4. 使用一些第三方日期类。

java7的时间类,还有一些其他的不方便的地方。
看下面示例

@Test
    public void getMonthDeom(){
        System.out.println("current time:"+new Date());
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        System.out.println("year number is:"+year);
        int month = calendar.get(Calendar.MONTH);
        System.out.println("month number is:"+month);
        
    }

执行完成后,得到的结果是

current time:Wed Mar 11 14:33:30 CST 2020
year number is:2020
month number is:2

可以看到,月份,是少1的,获取到的月份需要加1才是当前月份数字。感觉这个是不是程序员的毛病作祟,啥东西都要从0开始。

总之,java7的日期类使用起来,需要注意的地方较多,所以,在java8的时候,改进了不少。关于java8的日期类,请看下一篇,java8时间相关以及final修饰词

上一篇 下一篇

猜你喜欢

热点阅读