java的SimpleDateFormat线程不安全的几种解决方案

但我们知道SimpleDateFormat是线程不安全的,处理时要特别小心,要加锁或者不能定义为static,要在方法内new出对象,再进行格式化,本文就介绍了几种方法,感兴趣的可以了解一下

javaSimpleDateFormat线程不安全的几种解决方案,久久派带你了解更多相关信息。

目录
  • 场景
  • SimpleDateFormat线程为什么是线程不安全的呢?
    • 验证SimpleDateFormat线程不安全
  • 解决方案
    • 解决方案1:不要定义为static变量,使用局部变量
    • 解决方案2:加锁:synchronized锁和Lock锁 加synchronized锁
      • 加Lock锁
    • 解决方案3:使用ThreadLocal方式
      • 解决方案4:使用DateTimeFormatter代替SimpleDateFormat
        • 解决方案5:使用FastDateFormat 替换SimpleDateFormat
          • FastDateFormat源码分析
          • 实践
      • 结论

        场景

        在java8以前,要格式化日期时间,就需要用到SimpleDateFormat

        但我们知道SimpleDateFormat是线程不安全的,处理时要特别小心,要加锁或者不能定义为static,要在方法内new出对象,再进行格式化。很麻烦,而且重复地new出对象,也加大了内存开销。

        SimpleDateFormat线程为什么是线程不安全的呢?

        来看看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,如果在使用SimpleDateFormat时,用static定义,那SimpleDateFormat变成了共享变量。那SimpleDateFormat中的calendar就可以被多个线程访问到。

        SimpleDateFormat的parse方法也是线程不安全的:

         public Date parse(String text, ParsePosition pos)    {     ...         Date parsedDate;        try {            parsedDate = calb.establish(calendar).getTime();            // If the year value is ambiguous,            // then the two-digit year == the default start year            if (ambiguousYear[0]) {                if (parsedDate.before(defaultCenturyStart)) {                    parsedDate = calb.addYear(100).establish(calendar).getTime();                }            }        }        // An IllegalArgumentException will be thrown by Calendar.getTime()        // if any fields are out of range, e.g., MONTH == 17.        catch (IllegalArgumentException e) {            pos.errorIndex = start;            pos.index = oldStart;            return null;        }        return parsedDate;   }

        由源码可知,最后是调用**parsedDate = calb.establish(calendar).getTime();**获取返回值。方法的参数是calendar,calendar可以被多个线程访问到,存在线程不安全问题。

        我们再来看看**calb.establish(calendar)**的源码

        java的SimpleDateFormat线程不安全的几种解决方案

        calb.establish(calendar)方法先后调用了cal.clear()cal.set(),先清理值,再设值。但是这两个操作并不是原子性的,也没有线程安全机制来保证,导致多线程并发时,可能会引起cal的值出现问题了。

        验证SimpleDateFormat线程不安全

        public class SimpleDateFormatDemoTest {	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");    public static void main(String[] args) {    		//1、创建线程池        ExecutorService pool = Executors.newFixedThreadPool(5);        //2、为线程池分配任务        ThreadPoolTest threadPoolTest = new ThreadPoolTest();        for (int i = 0; i < 10; i++) {            pool.submit(threadPoolTest);        }        //3、关闭线程池        pool.shutdown();    }    static class  ThreadPoolTest implements Runnable{        @Override        public void run() {				String dateString = simpleDateFormat.format(new Date());				try {					Date parseDate = simpleDateFormat.parse(dateString);					String dateString2 = simpleDateFormat.format(parseDate);					System.out.println(Thread.currentThread().getName()+\" 线程是否安全: \"+dateString.equals(dateString2));				} catch (Exception e) {					System.out.println(Thread.currentThread().getName()+\" 格式化失败 \");				}        }    }}

        java的SimpleDateFormat线程不安全的几种解决方案

        出现了两次false,说明线程是不安全的。而且还抛异常,这个就严重了。

        解决方案

        解决方案1:不要定义为static变量,使用局部变量

        就是要使用SimpleDateFormat对象进行format或parse时,再定义为局部变量。就能保证线程安全。

        public class SimpleDateFormatDemoTest1 {    public static void main(String[] args) {    		//1、创建线程池        ExecutorService pool = Executors.newFixedThreadPool(5);        //2、为线程池分配任务        ThreadPoolTest threadPoolTest = new ThreadPoolTest();        for (int i = 0; i < 10; i++) {            pool.submit(threadPoolTest);        }        //3、关闭线程池        pool.shutdown();    }    static class  ThreadPoolTest implements Runnable{		@Override		public void run() {			SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");			String dateString = simpleDateFormat.format(new Date());			try {				Date parseDate = simpleDateFormat.parse(dateString);				String dateString2 = simpleDateFormat.format(parseDate);				System.out.println(Thread.currentThread().getName()+\" 线程是否安全: \"+dateString.equals(dateString2));			} catch (Exception e) {				System.out.println(Thread.currentThread().getName()+\" 格式化失败 \");			}		}    }}

        java的SimpleDateFormat线程不安全的几种解决方案

        由图可知,已经保证了线程安全,但这种方案不建议在高并发场景下使用,因为会创建大量的SimpleDateFormat对象,影响性能。

        解决方案2:加锁:synchronized锁和Lock锁 加synchronized锁

        SimpleDateFormat对象还是定义为全局变量,然后需要调用SimpleDateFormat进行格式化时间时,再用synchronized保证线程安全。

        public class SimpleDateFormatDemoTest2 {	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");    public static void main(String[] args) {    		//1、创建线程池        ExecutorService pool = Executors.newFixedThreadPool(5);        //2、为线程池分配任务        ThreadPoolTest threadPoolTest = new ThreadPoolTest();        for (int i = 0; i < 10; i++) {            pool.submit(threadPoolTest);        }        //3、关闭线程池        pool.shutdown();    }    static class  ThreadPoolTest implements Runnable{		@Override		public void run() {			try {				synchronized (simpleDateFormat){					String dateString = simpleDateFormat.format(new Date());					Date parseDate = simpleDateFormat.parse(dateString);					String dateString2 = simpleDateFormat.format(parseDate);					System.out.println(Thread.currentThread().getName()+\" 线程是否安全: \"+dateString.equals(dateString2));				}			} catch (Exception e) {				System.out.println(Thread.currentThread().getName()+\" 格式化失败 \");			}		}    }}

        java的SimpleDateFormat线程不安全的几种解决方案

        如图所示,线程是安全的。定义了全局变量SimpleDateFormat,减少了创建大量SimpleDateFormat对象的损耗。但是使用synchronized锁,
        同一时刻只有一个线程能执行锁住的代码块,在高并发的情况下会影响性能。但这种方案不建议在高并发场景下使用

        加Lock锁

        加Lock锁和synchronized锁原理是一样的,都是使用锁机制保证线程的安全。

        public class SimpleDateFormatDemoTest3 {	private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");	private static Lock lock = new ReentrantLock();    public static void main(String[] args) {    		//1、创建线程池        ExecutorService pool = Executors.newFixedThreadPool(5);        //2、为线程池分配任务        ThreadPoolTest threadPoolTest = new ThreadPoolTest();        for (int i = 0; i < 10; i++) {            pool.submit(threadPoolTest);        }        //3、关闭线程池        pool.shutdown();    }    static class  ThreadPoolTest implements Runnable{		@Override		public void run() {			try {				lock.lock();					String dateString = simpleDateFormat.format(new Date());					Date parseDate = simpleDateFormat.parse(dateString);					String dateString2 = simpleDateFormat.format(parseDate);					System.out.println(Thread.currentThread().getName()+\" 线程是否安全: \"+dateString.equals(dateString2));			} catch (Exception e) {				System.out.println(Thread.currentThread().getName()+\" 格式化失败 \");			}finally {				lock.unlock();			}		}    }}

        java的SimpleDateFormat线程不安全的几种解决方案

        由结果可知,加Lock锁也能保证线程安全。要注意的是,最后一定要释放锁,代码里在finally里增加了lock.unlock();,保证释放锁。
        在高并发的情况下会影响性能。这种方案不建议在高并发场景下使用

        解决方案3:使用ThreadLocal方式

        使用ThreadLocal保证每一个线程有SimpleDateFormat对象副本。这样就能保证线程的安全。

        public class SimpleDateFormatDemoTest4 {	private static ThreadLocal<DateFormat> threadLocal = new ThreadLocal<DateFormat>(){		@Override		protected DateFormat initialValue() {			return new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");		}	};    public static void main(String[] args) {    		//1、创建线程池        ExecutorService pool = Executors.newFixedThreadPool(5);        //2、为线程池分配任务        ThreadPoolTest threadPoolTest = new ThreadPoolTest();        for (int i = 0; i < 10; i++) {            pool.submit(threadPoolTest);        }        //3、关闭线程池        pool.shutdown();    }    static class  ThreadPoolTest implements Runnable{		@Override		public void run() {			try {					String dateString = threadLocal.get().format(new Date());					Date parseDate = threadLocal.get().parse(dateString);					String dateString2 = threadLocal.get().format(parseDate);					System.out.println(Thread.currentThread().getName()+\" 线程是否安全: \"+dateString.equals(dateString2));			} catch (Exception e) {				System.out.println(Thread.currentThread().getName()+\" 格式化失败 \");			}finally {				//避免内存泄漏,使用完threadLocal后要调用remove方法清除数据				threadLocal.remove();			}		}    }}

        java的SimpleDateFormat线程不安全的几种解决方案

        使用ThreadLocal能保证线程安全,且效率也是挺高的。适合高并发场景使用

        解决方案4:使用DateTimeFormatter代替SimpleDateFormat

        使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是线程安全的,java 8+支持)
        DateTimeFormatter介绍 传送门:万字博文教你搞懂java源码的日期和时间相关用法

        public class DateTimeFormatterDemoTest5 {	private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\");	public static void main(String[] args) {		//1、创建线程池		ExecutorService pool = Executors.newFixedThreadPool(5);		//2、为线程池分配任务		ThreadPoolTest threadPoolTest = new ThreadPoolTest();		for (int i = 0; i < 10; i++) {			pool.submit(threadPoolTest);		}		//3、关闭线程池		pool.shutdown();	}	static class  ThreadPoolTest implements Runnable{		@Override		public void run() {			try {				String dateString = dateTimeFormatter.format(LocalDateTime.now());				TemporalAccessor temporalAccessor = dateTimeFormatter.parse(dateString);				String dateString2 = dateTimeFormatter.format(temporalAccessor);				System.out.println(Thread.currentThread().getName()+\" 线程是否安全: \"+dateString.equals(dateString2));			} catch (Exception e) {				e.printStackTrace();				System.out.println(Thread.currentThread().getName()+\" 格式化失败 \");			}		}	}}

        java的SimpleDateFormat线程不安全的几种解决方案

        使用DateTimeFormatter能保证线程安全,且效率也是挺高的。适合高并发场景使用

        解决方案5:使用FastDateFormat 替换SimpleDateFormat

        使用FastDateFormat 替换SimpleDateFormat(FastDateFormat 是线程安全的,Apache Commons Lang包支持,不受限于java版本)

        public class DateTimeFormatterDemoTest5 {	private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd HH:mm:ss\");	public static void main(String[] args) {		//1、创建线程池		ExecutorService pool = Executors.newFixedThreadPool(5);		//2、为线程池分配任务		ThreadPoolTest threadPoolTest = new ThreadPoolTest();		for (int i = 0; i < 10; i++) {			pool.submit(threadPoolTest);		}		//3、关闭线程池		pool.shutdown();	}	static class  ThreadPoolTest implements Runnable{		@Override		public void run() {			try {				String dateString = dateTimeFormatter.format(LocalDateTime.now());				TemporalAccessor temporalAccessor = dateTimeFormatter.parse(dateString);				String dateString2 = dateTimeFormatter.format(temporalAccessor);				System.out.println(Thread.currentThread().getName()+\" 线程是否安全: \"+dateString.equals(dateString2));			} catch (Exception e) {				e.printStackTrace();				System.out.println(Thread.currentThread().getName()+\" 格式化失败 \");			}		}	}}

        使用FastDateFormat能保证线程安全,且效率也是挺高的。适合高并发场景使用

        FastDateFormat源码分析

         Apache Commons Lang 3.5

        //FastDateFormat@Overridepublic String format(final Date date) {    return printer.format(date);}@Override public String format(final Date date) {    final Calendar c = Calendar.getInstance(timeZone, locale);    c.setTime(date);    return applyRulesToString(c);}

        源码中 Calender 是在 format 方法里创建的,肯定不会出现 setTime 的线程安全问题。这样线程安全疑惑解决了。那还有性能问题要考虑?

        我们来看下FastDateFormat是怎么获取的

        FastDateFormat.getInstance();FastDateFormat.getInstance(CHINESE_DATE_TIME_PATTERN);

        看下对应的源码

        /** * 获得 FastDateFormat实例,使用默认格式和地区 * * @return FastDateFormat */public static FastDateFormat getInstance() {    return CACHE.getInstance();}/** * 获得 FastDateFormat 实例,使用默认地区 * 支持缓存 * * @param pattern 使用{@link java.text.SimpleDateFormat} 相同的日期格式 * @return FastDateFormat * @throws IllegalArgumentException 日期格式问题 */public static FastDateFormat getInstance(final String pattern) {    return CACHE.getInstance(pattern, null, null);}

        这里有用到一个CACHE,看来用了缓存,往下看

        private static final FormatCache < FastDateFormat > CACHE = new FormatCache < FastDateFormat > (){    @Override protected FastDateFormat createInstance(final String pattern, final TimeZone timeZone, final Locale locale) {        return new FastDateFormat(pattern, timeZone, locale);    }};//abstract class FormatCache<F extends Format>{    ... private final ConcurrentMap < Tuple, F > cInstanceCache = new ConcurrentHashMap <> (7);    private static final ConcurrentMap < Tuple, String > C_DATE_TIME_INSTANCE_CACHE = new ConcurrentHashMap <> (7);    ...}

        java的SimpleDateFormat线程不安全的几种解决方案

        在getInstance 方法中加了ConcurrentMap 做缓存,提高了性能。且我们知道ConcurrentMap 也是线程安全的。

        实践

        /** * 年月格式 {@link FastDateFormat}:yyyy-MM */public static final FastDateFormat NORM_MONTH_FORMAT = FastDateFormat.getInstance(NORM_MONTH_PATTERN);

        java的SimpleDateFormat线程不安全的几种解决方案

        //FastDateFormatpublic static FastDateFormat getInstance(final String pattern) {    return CACHE.getInstance(pattern, null, null);}

        java的SimpleDateFormat线程不安全的几种解决方案

        java的SimpleDateFormat线程不安全的几种解决方案

        如图可证,是使用了ConcurrentMap 做缓存。且key值是格式,时区和locale(语境)三者都相同为相同的key。

        结论

        这个是阿里巴巴 java开发手册中的规定:

        java的SimpleDateFormat线程不安全的几种解决方案

        1、不要定义为static变量,使用局部变量

        2、加锁:synchronized锁和Lock锁

        3、使用ThreadLocal方式

        4、使用DateTimeFormatter代替SimpleDateFormat(DateTimeFormatter是线程安全的,java 8+支持)

        5、使用FastDateFormat 替换SimpleDateFormat(FastDateFormat 是线程安全的,Apache Commons Lang包支持,java8之前推荐此用法)

        到此这篇关于java的SimpleDateFormat线程不安全的几种解决方案的文章就介绍到这了,更多相关java SimpleDateFormat线程不安全内容请搜索趣讯吧以前的文章或继续浏览下面的相关文章希望大家以后多多支持趣讯吧!

        版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请发送邮件至 55@qq.com 举报,一经查实,本站将立刻删除。转转请注明出处:https://www.szhjjp.com/n/11368.html

        (0)
        nan
        上一篇 2021-08-09
        下一篇 2021-08-09

        相关推荐

        • 狗丢了怎么办(家里狗丢了最有效的6招方法)

          摘要:狗狗丢了该怎么办?其实丢失的狗狗分为两种,第一种是无意走丢的,而第二种是有意走丢的!不管你家狗狗是有意走丢,还是无意走丢的,如果你平时和狗狗的关系很好的话,那么,你可以相信它会自己回来的。

          2023-05-05
          0
        • 无人机航拍时被鹰叼走 !网友:真是老鹰捉“无人机” 啊 !

          5月10日,湖南桑植一无人机在航拍时被鹰叼走。11日,当事人张女士称,开始以为碰拍到鸟了画面被撞,后面发现是鹰的爪子把无人机叼走了,在附近找了一个多小时还是没找到无人机。张雪介绍,当时起飞后都很顺利,拍摄了不到的2分钟的时间,就看见一只很大的鸟急速飞向无人机。“从它正面出现在画面中,再到抓住无人机带

          热点头条 2023-05-12
          0
        • 杭州一男子地铁车厢内狂撒5元纸币 ! 有乘客捡钱带走 !

          5月5日,浙江杭州,地铁19号线,一男子在车厢内发传单后狂撒钱,几乎都是5元纸币,乘客称目前此乘客已经被劝离,车厢也被保洁打扫干净了,有乘客捡钱带走。杭州地铁工作人员回应称,不允许这种不文明行为发生,遇到这种情况及时反馈,会有工作人员进行劝阻。网友调侃,没有100的,也得50的吧,5元的肯定会被谴责

          热点头条 2023-05-06
          0
        • 男子机动车道上骑鸵鸟 还拍视频炫耀 !360度全天窗,绿色环保,新能源座驾 !

          4月12日,四川绵阳一名男子骑着鸵鸟在马路上前行,而周围不断有车辆经过,遭到网友举报。13日,记者从绵阳公安交警直属五大队获悉,骑鸵鸟的男子是一名训练师,公安部门已对其进行批评教育,并告知其只能在自己园区内训练,不能上路,以免鸵鸟受惊发生意外。“我们接网友举报,称在涪城区关帝镇看到一名男子骑鸵鸟经过

          热点头条 2023-04-14
          0
        • 公司做假账怎么处罚

          法律问题分析:伪造、变造会计凭证、会计帐簿,编制虚假财务会计报告,构成犯罪的,依法追究刑事责任。法律依据:《中华人民共和国会计法》第四十三条伪造、变造会计凭证、会计帐簿,编制虚假财务会计报告,构成犯罪的,依法追究刑事责任。有前款行为,尚不构成犯罪

          2022-01-08
          0
        • 愿平安!中国籍远洋渔船印度洋倾覆 39人失联 !

          新华社北京5月17日电 5月16日凌晨3时许,蓬莱京鲁渔业有限公司所属中国籍远洋渔船“鲁蓬远渔028”在印度洋中部海域倾覆,船上39人失联,其中中国籍船员17人、印尼籍17人、菲律宾籍5人。截至目前,暂未发现失联人员,搜救工作正在进行中。除了组织救援船队前往事发海域进行搜救之外,相关部门还联系了国外

          热点头条 2023-05-17
          0

        发表回复

        登录后才能评论