之前在一本书上看到一个比较有意思的问题如下:
public class MyThread extends Thread {
private SimpleDateFormat dateFormat;
private String dateString;
MyThread(){
super();
}
MyThread(SimpleDateFormat dateFormat,String dateString){
super();
this.dateFormat =dateFormat;
this.dateString = dateString;
}
@Override
public void run() {
try{
java.util.Date dateStr = dateFormat.parse(dateString);
String newDateString = dateFormat.format(dateStr);
if(!newDateString.equals(dateString)){
System.out.println("时间转换出错了dateString="+dateString+"newDateString="+newDateString);
}
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public class FormatErrorTest {
public static void main(String[] args) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String[] dateStrings = new String[]{"2018-08-01","2018-08-02","2018-08-03","2018-08-04","2018-08-05","2018-08-06","2018-08-07","2018-08-08"};
MyThread[] myThreads = new MyThread[dateStrings.length];
for(int i=0;i<dateStrings.length;i++){
myThreads[i] = new MyThread(dateFormat,dateStrings[i]);
}
for(int i=0;i<dateStrings.length;i++){
myThreads[i].start();
}
}
}
-
思考这个main方法执行会不会出现问题?
运行结果如下:
image.png
其实SimpleDateFormat 是线程不安全。我自己看了一下源代码
其实SimpleDateFormat 这个类我们主要用的还是他的parse方法和format方法。
部分源码如下:
public Date parse(String text, ParsePosition pos){
//...
}
//StringBuffer 虽然是线程安全的,但是不能保证在参数传入阶段的线程同步问题,只能保证其每次只能同时被一个线程使用。
public StringBuffer format(Date date, StringBuffer toAppendTo,
FieldPosition pos){
//...
}
这2个方法明显没有做线程安全的控制,所以有线程安全问题也是可以理解的。如果线程A正在使用这个实例那个这个时候线程B又传入了新的时间字符串将会导致线程A得到的结果实际上可能是线程B需要的结果。
对于SimpleDateFormat这个类,建议在使用的时候一个线程一个实例,不要当做共享资源来使用,除非你显示了对这个类的实例的获取进行了特殊的线程安全处理。目前有2种比较通用的方法来解决这个问题。如下:
方法一,一个线程一个SimpleDateFormat实例
public class FormatErrorTest {
public static void main(String[] args) {
String[] dateStrings = new String[]{"2018-08-01","2018-08-02","2018-08-03","2018-08-04","2018-08-05","2018-08-06","2018-08-07","2018-08-08"};
MyThread[] myThreads = new MyThread[dateStrings.length];
for(int i=0;i<dateStrings.length;i++){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
myThreads[i] = new MyThread(dateFormat,dateStrings[i]);
}
for(int i=0;i<dateStrings.length;i++){
myThreads[i].start();
}
}
运行结果如下:
image.png
方法二:使用ThreadLocal处理,本质上原理都是差不多,就是在使用的时候如果ThreadLocal上面没有就新建一个放进去,如果有就直接获取。
public class FormatErrorTest {
static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>();
static SimpleDateFormat getSimpleDateFormat(){
SimpleDateFormat dateFormat = null;
dateFormat = local.get();
if(dateFormat==null){
System.out.println(Thread.currentThread().getName()+"没有抢到SimpleDateFormat");
dateFormat =new SimpleDateFormat("yyyy-MM-dd");
local.set(dateFormat);
}
return dateFormat;
}
public static void main(String[] args) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
String[] dateStrings = new String[]{"2018-08-01","2018-08-02","2018-08-03","2018-08-04","2018-08-05","2018-08-06","2018-08-07","2018-08-08"};
MyThread[] myThreads = new MyThread[dateStrings.length];
for(int i=0;i<dateStrings.length;i++){
myThreads[i] = new MyThread(dateFormat,dateStrings[i]);
}
for(int i=0;i<dateStrings.length;i++){
myThreads[i].start();
}
}
}
Mythread改成:
import java.text.Format;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
*
* @Author: GuiRunning 郭贵荣
*
* @Description:
*
* @Date: 2018/6/30 11:08
*
*/
public class MyThread extends Thread {
private String dateString;
MyThread(){
super();
}
MyThread(SimpleDateFormat dateFormat,String dateString){
super();
this.dateString = dateString;
}
@Override
public void run() {
try{
java.util.Date dateStr = FormatErrorTest.getSimpleDateFormat().parse(dateString);
String newDateString = FormatErrorTest.getSimpleDateFormat().format(dateStr);
if(!newDateString.equals(dateString)){
System.out.println("时间转换出错了dateString="+dateString+"newDateString="+newDateString);
}
System.out.println(Thread.currentThread().getName()+"完成转换dateString="+dateString+"newDateString="+newDateString);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
运行结果:
image.png
网友评论