【Java线程】SimpleDateFormat的线程安全性实验二
【实验目的】
从SimpleDateFormat着手来探讨怎样的代码会导致线程不安全。
【代码】
用于将java.util.Date变成时间字符串的DateUtil类:
package unsafesdf2; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { private static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); public static String format(Date date) { return sdf.format(date); } }
使用无限循环取日期的测试线程类:
package unsafesdf2; import java.util.Date; public class TestThread extends Thread{ private Date date; private String expect; public TestThread(Date date) { this.date=date; this.expect=DateUtil.format(date); } public void run() { while(true) { String actual=DateUtil.format(date); if(actual.equals(expect)==false) { System.out.println("expect:"+expect+" actual:"+actual+" @"+this.getName()); } } } }
程序启动类:
package unsafesdf2; import java.util.Calendar; public class Test { public static void main(String[] args) { Calendar clder=Calendar.getInstance(); clder.set(2022, 7-1, 23); TestThread th1=new TestThread(clder.getTime()); th1.setName("线程一"); clder.set(2022, 8-1, 24); TestThread th2=new TestThread(clder.getTime()); th2.setName("线程二"); th1.start(); th2.start(); } }
【程序输出节选】
expect:2022-08-24 actual:2022-08-23 @线程二 expect:2022-07-23 actual:2022-08-24 @线程一 expect:2022-08-24 actual:2022-08-23 @线程二 expect:2022-07-23 actual:2022-08-24 @线程一 expect:2022-08-24 actual:2022-07-23 @线程二 expect:2022-07-23 actual:2022-07-24 @线程一 expect:2022-08-24 actual:2022-08-23 @线程二 expect:2022-07-23 actual:2022-07-24 @线程一 expect:2022-08-24 actual:2022-07-23 @线程二
点评:
在Test类代码中,线程一被赋予了2022-07-23的时间,而线程二被赋予了2022-08-24的时间。
但实际输出中发现,除了年份都是2022没有发生错乱,月份日子都乱了。
需要注意的是,编写TestThread时是知道会发生错乱而把实际和预期不一致的情况输出,而在实际生产中,如果不知道可能发生并发赋值问题或是忽略了,出错了一段事件后才会由人工觉察出来,但此时很多入库的紊乱日期已经不可能还原了。
【原因】
SimpleDateFormat有一个类变量canlendar,在格式化日期时,canlendar变量会首先设置为用户的输入值。
这样当多个线程同时调用format函数,就有可能引发calendar并发赋值问题,即线程A处理完毕准备返回canlendar变量的值时,线程B闯入修改了canlendar变量的值,导致线程A取得了线程B带入的新值。
【什么样的代码会导致并发赋值问题】
void func(T value){
T v=value;
processV....
return v;
}
只要类似上面的,有从参数取值,处理值,返回值三步的,在多线程环境下,都有可能出问题。
即使是i++,++i这类代码,其内部实现也是分取值,设值,返回三步的,一样存在并发赋值问题。
总结下来就是:分取值,设值,返回三步曲的函数,如果不设置并发锁,任由线程进入的话,就存在并发赋值问题。
【参考资料】
《Java系统性能优化实战》P56-57 李家智 张世敏著 电子工业出版社出版。
END
浙公网安备 33010602011771号