• java:SimpleDateFormat线程不安全解决方式


    java:SimpleDateFormat线程不安全解决方式

    1 前言

    SimpleDateFormat定义为static时,多线程环境下共用此对象是线程不安全的。如下采用双重检测锁,为每个线程单独创建SimpleDateFormat对象,即可避免此问题。

    2 使用

    单例模式常见双重检测锁的使用:

    package com.xiaoxu.crawler.utils;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.CountDownLatch;
    
    /**
     * @author xiaoxu
     * @date 2022-11-22 0:00
     * crawlerJ:com.xiaoxu.crawler.utils.DateUtils
     */
    public class DateUtils {
    
        private static final Object lockObj = new Object();
    
        private static volatile Map<String, ThreadLocal<SimpleDateFormat>> timeLocalMaps = new HashMap<>();
    
        private static SimpleDateFormat getSDF(String pattern){
            AssertUtils.assertNonEmpty(pattern, "time pattern should not be null or empty.");
            ThreadLocal<SimpleDateFormat> local = timeLocalMaps.get(pattern);
            /* 双重检测锁,为timeLocalMaps增加volatile标识,
            * 禁止指令重排 */
            if(null == local){
                synchronized (lockObj){
                    local = timeLocalMaps.get(pattern);
                    if(null == local){
                        local = ThreadLocal.withInitial(()->new SimpleDateFormat(pattern));
                        timeLocalMaps.put(pattern, local);
                    }
                }
            }
            return local.get();
        }
    
        public static void main(String[] args) {
            long start  = System.currentTimeMillis();
            CountDownLatch countDownLatch = new CountDownLatch(100000);
            for (int i = 0; i < 1000; i++) {
                for(int j = 0; j<100; j++) {
                    new Thread(() -> {
                        try {
    	                    // 每个子线程单独创建sdf变量
                            SimpleDateFormat sdf = DateUtils.getSDF("yyyy-MM-dd");
                            Date parse = null;
                            try {
                                parse = sdf.parse("2022-11-10");
                            } catch (Exception e) {
                                System.out.println("错误!!!!");
                                throw new RuntimeException(e);
                            }
                            System.out.println("日期是" + parse);
                        } finally {
                            countDownLatch.countDown();
                        }
                    }, "thread-" + j).start();
                }
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            long end = System.currentTimeMillis();
            System.out.println("总耗时:"+(end - start)+"ms");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    package com.xiaoxu.crawler.utils;
    
    import com.xiaoxu.crawler.excp.CrawlerForJException;
    import org.apache.commons.lang3.ArrayUtils;
    
    /**
     * @author xiaoxu
     * @date 2022-11-06 15:50
     * crawlerJ:com.xiaoxu.crawler.utils.AssertUtils
     */
    public class AssertUtils {
        /* 校验为真 */
        public static void assertTrue(boolean res, String errorMsg){
            handlerError(res, errorMsg);
        }
    
        /* 校验非空 */
        public static <T> void assertNonNull(T obj, String errorMsg){
            handlerError(null != obj, errorMsg);
        }
    
        /* 校验非null非empty字符串 */
        public static <T> void assertNonEmpty(String str, String errorMsg){
            handlerError(null != str && !str.isEmpty(), errorMsg);
        }
    
        /* 校验非空Array */
        public static <T> void assertNonEmptyArray(T[] array, String errorMsg){
            handlerError(!ArrayUtils.isEmpty(array), errorMsg);
        }
    
        /* 统一异常处理 */
        private static void handlerError(boolean flag, String message){
            if(!flag){
                /* 使用公共异常处理 */
                throw new CrawlerForJException(message);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    执行结果无误:

    在这里插入图片描述

    注意:因为加锁和释放锁,会自动将线程变量写回到主内存中的timeLocalMaps变量,实际上述timeLocalMaps是线程间可见的。只是单例模式下,避免指令重排多次创建对象,违背了单例的初衷,故而在单例模式下,双重检测锁常与volatile(禁止指令重排)一同使用。

  • 相关阅读:
    如何从零开始拆解uni-app开发的vue项目(一)
    微服务实战 07Spring Cloud Gateway 入门与实战
    Java中的抽象类与接口介绍
    ​力扣解法汇总1796. 字符串中第二大的数字
    走进 Orca 架构及技术世界
    《简约至上》读书笔记
    T246836 [LSOT-1] 暴龙的土豆
    毕业设计选题uniapp+springboot新闻资讯小程序源码 开题 lw 调试
    【蓝桥杯冲击国赛计划第3天】队列
    Linux 中变量的取用与设定
  • 原文地址:https://blog.csdn.net/a232884c/article/details/127981366