栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Java

Java基础之SimpleDateFormat的多线程陷阱

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java基础之SimpleDateFormat的多线程陷阱

        SimpleDateFormat类我们可太熟了。Date转String、String转Date,我们不可避免的用到SimpleDateFormat,而且用起来非常简单。

public static void main(String[] args) throws ParseException {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    System.out.println(sdf.format(new Date()));
    String dateString = "2022-08-04 09:03:00";
    System.out.println(sdf.parse(dateString));
}

        这样短短几行代码就搞定了从Date转String和从String转Date。这样方便易用的类,我们怎能不爱。对于经常使用Date类型转换的项目,我们会把整个Date转换封装成一个工具类。

public class DateUtil {

    public static String dateToDateString(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    public static Date dateStringToDate(String dateString) throws ParseException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.parse(dateString);
    }
}

        这样每次创建一个SimpleDateFormat实例是不是不够节检,每次调用方法,创建实例也是一笔小小的开销,自古节俭都是美德,我把它优化成静态常量,可以创建一次全局调用,我可真是个小机灵。

public class DateUtil {

    public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String dateToDateString(Date date) {
        return sdf.format(date);
    }

    public static Date dateStringToDate(String dateString) throws ParseException {
        return sdf.parse(dateString);
    }
}

        这时候我们就掉进了陷阱中,我们精心为自己设计的陷阱:在简单环境中这种写法不会出现问题,但是在公司业务渐入佳境,并发量上来的时候,非线程安全的问题就会暴露出来。

Parse方法部分
public class SimpleDateFormatTest {

    public static void main(String[] args) {
        String[] dateStr = {
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12"
        };


        //实在多线程的环境下会出现问题
        new Thread(() -> {
            for (String s : dateStr) {
                Date date = null;
                try {
                    date = DateUtil.dateStringToDate(s);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + date);
            }
        }).start();


        new Thread(() -> {
            for (String s : dateStr) {
                Date date = null;
                try {
                    date = DateUtil.dateStringToDate(s);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + date);
            }
        }).start();
    }
}

        这段代码在执行的时候会出现异常。这个报错是提示我数字格式化异常,存在多个小数点。别问,问就是吃了没文化的亏。

        想知道为什么会产生这个错误,我们就需要深入源码查看一下了。我们调用的parse(String)经过调用一系列重载重写方法后,进入到SimpleDateFormat的parse(String text, ParsePosition pos)方法。有些人抄来抄去说这块是Calendar的原因,但是其实这块还不是Calendar的原因。找到出问题的getDouble函数。 

        在SimpleDateFormat被static修饰的时候,我们就共用了这个DigitList类的getDouble方法,在多线程场景下自然而然的发生很多奇怪的问题。 

Format方法部分

        当然了,毕竟SimpleDateFormat类非线程安全,所以format方法也会有线程安全问题。而format方法的问题出在Calendar。Calendar是SimpleDateFormat私有变量。

private StringBuffer format(Date date, StringBuffer toAppendTo,
                            FieldDelegate delegate) {
    // Convert input date to time field list
    calendar.setTime(date);

    boolean useDateFormatSymbols = useDateFormatSymbols();

    for (int i = 0; i < compiledPattern.length; ) {
        int tag = compiledPattern[i] >>> 8;
        int count = compiledPattern[i++] & 0xff;
        if (count == 255) {
            count = compiledPattern[i++] << 16;
            count |= compiledPattern[i++];
        }

        switch (tag) {
        case TAG_QUOTE_ASCII_CHAR:
            toAppendTo.append((char)count);
            break;

        case TAG_QUOTE_CHARS:
            toAppendTo.append(compiledPattern, i, count);
            i += count;
            break;

        default:
            subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
            break;
        }
    }
    return toAppendTo;
}
解决办法

        既然我们知道了问题是怎样产生的,那么再来看一下问题需要怎么解决。

        1.使用ThreadLocal

                我觉得使用ThreadLocal是最可以再面试中吹牛皮的。让面试官眼前一亮。

public class DateUtil {

    // 优化后
    private static ThreadLocal threadLocal = new ThreadLocal(){
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    // 优化前
    // public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String dateToDateString(Date date) {
        String dateString = threadLocal.get().format(date);
        // 记得remove,以免内存泄漏
        threadLocal.remove();
        return dateString;
    }

    public static Date dateStringToDate(String dateString) throws ParseException {
        Date date = threadLocal.get().parse(dateString);
        // 记得remove,以免内存泄漏
        threadLocal.remove();
        return date;
    }
}
        2.在代码中加入synchronized方法块

                可以解决问题,但不够秀。有点东西但不多!而且将日期转换变成单线程执行,多个线程争抢,会导致效率降低。

public class SimpleDateFormatTest {

    public static void main(String[] args) {
        String[] dateStr = {
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12",
                "2020-12-12 12:12:12"
        };


        //实在多线程的环境下会出现问题
        new Thread(() -> {
            for (String s : dateStr) {
                Date date = null;
                try {
                    synchronized (SimpleDateFormatTest.class) {
                        date = DateUtil.dateStringToDate(s);
                    }
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + date);
            }
        }).start();


        new Thread(() -> {
            for (String s : dateStr) {
                Date date = null;
                try {
                    synchronized (SimpleDateFormatTest.class) {
                        date = DateUtil.dateStringToDate(s);
                    }
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + date);
            }
        }).start();
    }
}
        3.使用DateTimeFormatter

                DateTimeFormatter是JDK1.8开始对外提供服务的线程安全的日期格式化类。不确定面试官懂不懂这个类,没准可以装个比。

public class DateUtil {

    // 优化前
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.systemDefault());
    // 优化后
    public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static String dateToDateString(Date date) {
        return DATE_TIME_FORMATTER.format(LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()));
    }

    public static Date dateStringToDate(String dateString) throws ParseException {
        return Date.from(Instant.from(DATE_TIME_FORMATTER.parse(dateString)));
    }
}
转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1039050.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号