美文网首页
java基础-时间相关(篇一 java7)

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

作者: Mark_Du | 来源:发表于2020-03-11 14:44 被阅读0次

    简单介绍一下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修饰词

    相关文章

      网友评论

          本文标题:java基础-时间相关(篇一 java7)

          本文链接:https://www.haomeiwen.com/subject/rbfenctx.html