SimpleDateFormat和ThreadLocal联合使用

ⅰ亾dé卋堺 提交于 2020-08-16 12:08:07

SimpleDateFormat线程不安全问题

SimpleDateFormat大家都用过,日期与字符串转换的类,它的方法是线程不安全的。有同学就说了,这个方法不安全也没事啊,不就是做个日期转换,现编写一下代码

package com.huawei.test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;

public class ProveNotSafe {
    public static void main(String[] args) {
        for (int i=0;i<5;i++){
            new Thread(new DateFormatTest()).start();
        }
    }
}

class DateFormatTest implements Runnable{
    static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+":"+df.parse("2020-09-11"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

运行完后,妥妥的报错了

Thread-4:Fri Sep 11 00:00:00 CST 2020
Exception in thread "Thread-1" java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Long.parseLong(Long.java:601)
	at java.lang.Long.parseLong(Long.java:631)
	at java.text.DigitList.getLong(DigitList.java:195)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.huawei.test.DateFormatTest.run(ProveNotSafe.java:21)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-2" Exception in thread "Thread-3" java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.huawei.test.DateFormatTest.run(ProveNotSafe.java:21)
	at java.lang.Thread.run(Thread.java:748)
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.huawei.test.DateFormatTest.run(ProveNotSafe.java:21)
	at java.lang.Thread.run(Thread.java:748)
Thread-0:Fri Sep 11 00:00:00 CST 2020
Disconnected from the target VM, address: '127.0.0.1:56922', transport: 'socket'

Process finished with exit code 0

没错就是java.lang.NumberFormatException: multiple points,不怎么常见,跟进异常堆栈,定位到的点是使用parse方法时会把继承自DateFormat类的类变量calendar被clear了正在设置新值,刚好其他线程进来时使用也调用了calendar.clear(),导致当前线程挂壁,则报错。

如果是用format方法倒是不会报错,但是会串其他线程的结果。原因也是一样:类变量Calendar多线程

解决方案

方案一:每次都new 一个SimpleDateFormat

每次线程使用新的实例对象,在本例也简单,把static去掉即可。但是消耗的内存过多

方案二:将对象存入ThreadLocal中

class DateFormatTest implements Runnable{
	// 将SimpleDateFormat对象放入ThreadLocal,线程只用自己的对象,就不会出现线程共享脏用报错的问题
    private ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
        public SimpleDateFormat initialValue(){
            SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd", Locale.US);
            return sdf;
        }
    };
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName()+":"+threadLocal.get().parse("2020-09-11"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

方案三:使用JDK8提供的DateTimeFormatter

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss SSS");
LocalDateTime now = LocalDateTime.now();
String str = dtf.format(now);

方案四:将SimpleDateFormat对象用sychronized关键字修饰

synchronized (df) {
	System.out.println(Thread.currentThread().getName() + ":" + df.parse("2020-09-11"));
}

LocalThread扩展知识

LocalThread的底层实现

LocalThread造成的内存泄漏

每个Thread都有ThreadLocal.ThreadLocalMap类型的变量,这个map中的Entry继承了WeakRefrence弱引用,就可以被GC。

static class Entry extends WeakReferene<ThreadLocal<?>>{
	Object value;
	Entry(ThreadLocal<?> k,Object v){
		super(k);
		value = v;
	}
}

但是value=v这行代码是强引用,一直是可达的,所以不会被回收,用remove方法就可以删除对应的value,避免内存泄漏。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!