简单介绍一下java时间相关的操作,以及线程并发相关的一些问题
首先,java时间相关类,常见的情况,分java7和java8两个版本来讨论。
- 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);
被交叉调用,得到的结果不是自己想要的。
解决办法有几种
- 在每次使用的地方new一个SimpleDateFormat,不重复使用即可
当然,这也会带来性能的损耗,在一些大型性能,每个地方的损耗都会考虑到。 - 在使用的地方加锁,这个会消耗性能。
- 使用 ThreadLocal,每个线程用自己的SimpleDateFormat
- 使用一些第三方日期类。
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修饰词
网友评论