簡單介紹一下java時間相關(guān)的操作,以及線程并發(fā)相關(guān)的一些問題
首先,java時間相關(guān)類喊递,常見的情況,分java7和java8兩個版本來討論阳似。
- java7相關(guān)的時間操作
@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
//字符串轉(zhuǎn)時間戳
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
//時間戳轉(zhuǎn)字符串
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
//與時區(qū)相關(guān)
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
}
代碼這里使用了單元測試的方式來寫的骚勘,不會單元測試的同學(xué),自己加一個main方法來調(diào)用也可以撮奏。
時間相關(guān)的操作俏讹,做的最多就是:時間戳和年月日字符串相互轉(zhuǎn)換
常見的就是,存入數(shù)據(jù)庫的時候存入時間戳畜吊,提取出來給頁面的時候泽疆,需要給年月日字符串。
這里就分化出一些操作玲献,比如殉疼,根據(jù)時區(qū)進行轉(zhuǎn)換梯浪,或者提取單獨的年,月瓢娜,日等挂洛。
整個轉(zhuǎn)換過程中,最核心的類就是 SimpleDateFormat
這個SimpleDateFormat
存在一點問題眠砾,就是 線程不安全
首先解釋下 線程不安全
大致意思就是虏劲,在多線程環(huán)境下使用存在一定風(fēng)險
先看下面這個例子
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()+"-執(zhí)行前count="+count);
try {
count++;
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-執(zhí)行后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++
類型的多線程調(diào)用
執(zhí)行一下褒颈,其中一次的結(jié)果是這樣的
線程1-執(zhí)行前count=1
線程3-執(zhí)行前count=1
線程3-執(zhí)行后count=3
線程2-執(zhí)行前count=1
線程3-執(zhí)行前count=3
線程1-執(zhí)行后count=2
線程1-執(zhí)行前count=5
線程1-執(zhí)行后count=6
線程3-執(zhí)行后count=5
線程2-執(zhí)行后count=4
線程3-執(zhí)行前count=6
線程3-執(zhí)行后count=7
線程1-執(zhí)行前count=6
線程3-執(zhí)行前count=7
線程3-執(zhí)行后count=9
線程3-執(zhí)行前count=9
線程2-執(zhí)行前count=6
線程3-執(zhí)行后count=10
線程1-執(zhí)行后count=8
線程2-執(zhí)行后count=11
執(zhí)行結(jié)果每次都不一樣
然后柒巫,我們只啟動線程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()+"-執(zhí)行前count="+count);
try {
count++;
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-執(zhí)行后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();
}
}
得到的結(jié)果非常穩(wěn)定
線程1-執(zhí)行前count=1
線程1-執(zhí)行后count=2
線程1-執(zhí)行前count=2
線程1-執(zhí)行后count=3
線程1-執(zhí)行前count=3
線程1-執(zhí)行后count=4
線程1-執(zhí)行前count=4
線程1-執(zhí)行后count=5
線程1-執(zhí)行前count=5
線程1-執(zhí)行后count=6
線程1-執(zhí)行前count=6
線程1-執(zhí)行后count=7
線程1-執(zhí)行前count=7
線程1-執(zhí)行后count=8
線程1-執(zhí)行前count=8
線程1-執(zhí)行后count=9
線程1-執(zhí)行前count=9
線程1-執(zhí)行后count=10
多線程執(zhí)行過長中count的值是很亂的,而且最后出現(xiàn)了一個11
整個代碼執(zhí)行的過程哈肖,可以分解一下
1.比較count的值
2.對當(dāng)前count值加1
這兩個步驟吻育,在這段代碼里念秧,是非原子
的
原子性
這個概念淤井,表明一系列操作,一系列執(zhí)行動作摊趾,像原子一樣币狠,不可分割,一系列的操作砾层,要么全執(zhí)行漩绵,要么一個都不執(zhí)行,沒有中間狀態(tài)
放到多線程環(huán)境下肛炮,非原子
的多步驟操作止吐,就會出現(xiàn)線程安全的問題
在我們這種常規(guī)的應(yīng)用程序中,在多線程的并發(fā)環(huán)境下侨糟,如果沒有使用額外的同步手段來處理并發(fā)問題碍扔,那么,線程的調(diào)度秕重,是不可控不同,不可預(yù)期的,誰先執(zhí)行溶耘,誰后執(zhí)行二拐,是不確定的
最后出現(xiàn)11的情況可能是這樣的:
1. 線程2拿到count值為9,小于10
同時線程3也拿到count值9
2. 線程3先執(zhí)行自增凳兵,這時count值為10
3. 線程2執(zhí)行自增百新,這時count值已經(jīng)是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()
然后把它放到多線程環(huán)境下使用巷挥,就會出現(xiàn)上面我們demo里面的情況
calendar.setTime(date);
被交叉調(diào)用桩卵,得到的結(jié)果不是自己想要的。
解決辦法有幾種
- 在每次使用的地方new一個SimpleDateFormat倍宾,不重復(fù)使用即可
當(dāng)然雏节,這也會帶來性能的損耗,在一些大型性能高职,每個地方的損耗都會考慮到钩乍。 - 在使用的地方加鎖,這個會消耗性能怔锌。
- 使用 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);
}
執(zhí)行完成后,得到的結(jié)果是
current time:Wed Mar 11 14:33:30 CST 2020
year number is:2020
month number is:2
可以看到岛杀,月份阔拳,是少1的,獲取到的月份需要加1才是當(dāng)前月份數(shù)字类嗤。感覺這個是不是程序員的毛病作祟糊肠,啥東西都要從0開始。
總之遗锣,java7的日期類使用起來货裹,需要注意的地方較多,所以黄伊,在java8的時候泪酱,改進了不少。關(guān)于java8的日期類还最,請看下一篇墓阀,java8時間相關(guān)以及final修飾詞