首页 > 编程语言 >Java中为什么禁止把SimpleDateFormat定位为static变量以及如果非要使用static定位SimpleDateFormat时在多线程环境下的几种使用方式

Java中为什么禁止把SimpleDateFormat定位为static变量以及如果非要使用static定位SimpleDateFormat时在多线程环境下的几种使用方式

时间:2023-06-05 19:22:12浏览次数:57  
标签:线程 private SimpleDateFormat static new calendar 多线程

场景

Java中ExecutorService线程池的使用(Runnable和Callable多线程实现):

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126242904

Java中创建线程的方式以及线程池创建的方式、推荐使用ThreadPoolExecutor以及示例:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/130068794

Java中使用CountDownLatch实现并发流程控制:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/126255413

以下会用到如上概念。

Java开发手册中对于SimpleDateFormat的使用的要求是:

【强制】SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,

必须加锁,或者使用 DateUtils 工具类。

正例:

注意线程安全,使用 DateUtils。亦推荐如下处理:

        private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
         @Override
         protected DateFormat initialValue() {
         return new SimpleDateFormat("yyyy-MM-dd");
         }
        };

说明:如果是 JDK8 的应用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,

DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释:

simple beautiful strong immutable thread-safe。

 

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

为了验证以上结论,首先需要了解下时区的概念。

时区是地球上的区域使用同一个时间定义。

以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。

1863 年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。

世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。

这些偏差就是所谓的时差。现今全球共分为 24 个时区。

由于实用上常常 1 个国家,或 1 个省份同时跨着 2个或更多时区,为了照顾到行政上的方便,常将 1 个国家或 1 个省份划在一起。

所以时区并不严格按南北直线来划分,而是按自然条件来划分。

例如,中国幅员宽广,差不多跨 5 个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。

由于不同的时区的时间是不一样的,甚至同一个国家的不同城市时间都可能不一样,所以,

在 Java 中想要获取时间的时候,要重点关注一下时区问题。

默认情况下,如果不指明,在创建日期的时候,会使用当前计算机所在的时区作为默认时区,

这也是为什么我们通过只要使用new  Date()就可以获取中国的当前时间的原因。

Java中输出不同时区的时间

Java中可以通过SimpleDateFormat实现获取不同时区的时间

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

sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));

System.out.println(sdf.format(Calendar.getInstance().getTime()));

以上输出纽约的时间比中国北京时间早了12个小时

验证如果将SimpleDateFormat声明为static会如何

新建测试类TestStaticSDF

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.*;

public class TestStaticSDF {
    //定义全局SimpleDateFormat
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //使用guava的ThreadFactoryBuilder定义一个线程池
    private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
    private static ExecutorService pool =
            new ThreadPoolExecutor(5,
                    200,
                    0L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(1024),
                    threadFactory,
                    new ThreadPoolExecutor.AbortPolicy());
    //定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(()->{
                //时间增加
                calendar.add(Calendar.DATE,finalI);
                String dateString = simpleDateFormat.format(calendar.getTime());
                dates.add(dateString);
                countDownLatch.countDown();
            });
        }
        //堵塞,直到countDown数量为0
        countDownLatch.await();
        System.out.println(dates.size());//60
    }
}

就是循环100次,每次循环的时候都在当前时间基础上增加一个天数,然后把所有日期放在一个线程安全的、带有去重功能的Set中,

然后输出set的元素个数。

这里要注意:

在Java中,Collections类提供了许多线程安全的方法来处理集合类,其中一个重要的方法就是synchronizedSet()。

这是一个可以将任何Set集合转换为线程安全的Set集合的方法。

实际结果却是小于100的值,原因就是因为 SimpleDateFormat 作为一个非线程安全的类,

被当做了共享变量在多个线程中进行使用,这就出现了线程安全问题。

查看format的源码,方法在执行过程中,会使用一个成员变量calendar 来保存时间。

 

 

由于我们在声明 SimpleDateFormat 的时候,使用的是 static 定义的。

那么这 个 SimpleDateFormat 就 是 一 个 共 享 变 量, 随 之,SimpleDateFormat 中 的calendar 也就可以被多个线程访问到。

假设线程 1 刚刚执行完calendar.setTime把时间设置成 2018-11-11,还没等执行完,线程 2 又执行了calendar.setTime把时间改成了 2018-12-12。

这时候线程 1 继续往下执行,拿到的calendar.getTime得到的时间就是线程 2 改过之后的。

除了 format 方法以外,SimpleDateFormat 的 parse 方法也有同样的问题。所以,不要把 SimpleDateFormat 作为一个共享变量使用。

Java中多线程环境下使用static的SimpleDateFormat的解决方式

第一种方式,将SimpleDateFormat声明为局部变量,就不会被多个线程同时访问到了,避开线程安全问题

public class TestStaticSDFSolve {
    //使用guava的ThreadFactoryBuilder定义一个线程池
    private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
    private static ExecutorService pool =
            new ThreadPoolExecutor(5,
                    200,
                    0L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(1024),
                    threadFactory,
                    new ThreadPoolExecutor.AbortPolicy());
    //定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(()->{
                //SimpleDateFormat 声明成局部变量
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                //时间增加
                calendar.add(Calendar.DATE,finalI);
                String dateString = simpleDateFormat.format(calendar.getTime());
                dates.add(dateString);
                countDownLatch.countDown();
            });
        }
        //堵塞,直到countDown数量为0
        countDownLatch.await();
        System.out.println(dates.size());//100
    }

}

第二种方式,对于共享变量加锁,通过加锁,使多个线程排队顺序执行,避免了并发导致的线程安全问题

public class TestStaticSDFSolve2 {
    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    //使用guava的ThreadFactoryBuilder定义一个线程池
    private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
    private static ExecutorService pool =
            new ThreadPoolExecutor(5,
                    200,
                    0L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(1024),
                    threadFactory,
                    new ThreadPoolExecutor.AbortPolicy());
    //定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(()->{
                synchronized (simpleDateFormat){
                    //时间增加
                    calendar.add(Calendar.DATE,finalI);
                    String dateString = simpleDateFormat.format(calendar.getTime());
                    dates.add(dateString);
                    countDownLatch.countDown();
                }
            });
        }
        //堵塞,直到countDown数量为0
        countDownLatch.await();
        System.out.println(dates.size());//100
    }
}

第三种方式,就是使用ThreadLocal。可以确保每个线程都可以得到单独的一个SimpleDateFormat的对象

public class TestStaticSDFSolve3 {
    private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal
            .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    //使用guava的ThreadFactoryBuilder定义一个线程池
    private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
    private static ExecutorService pool =
            new ThreadPoolExecutor(5,
                    200,
                    0L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(1024),
                    threadFactory,
                    new ThreadPoolExecutor.AbortPolicy());
    //定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            Calendar calendar = Calendar.getInstance();
            int finalI = i;
            pool.execute(()->{
                //时间增加
                calendar.add(Calendar.DATE,finalI);
                String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());
                dates.add(dateString);
                countDownLatch.countDown();
            });
        }
        //堵塞,直到countDown数量为0
        countDownLatch.await();
        System.out.println(dates.size());//100
    }
}

第四种方式,如果是java8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类public class

 

 TestStaticSDFSolve4 {
    private static DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    //使用guava的ThreadFactoryBuilder定义一个线程池
    private static ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
    private static ExecutorService pool =
            new ThreadPoolExecutor(5,
                    200,
                    0L,
                    TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<>(1024),
                    threadFactory,
                    new ThreadPoolExecutor.AbortPolicy());
    //定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
    private static CountDownLatch countDownLatch = new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程安全的HashSet
        Set<String> dates = Collections.synchronizedSet(new HashSet<String>());
        for (int i = 0; i < 100; i++) {
            //获取当前时间
            LocalDateTime now = LocalDateTime.now();
            int finalI = i;
            pool.execute(()->{
                //时间增加
                LocalDateTime localDateTime = now.plusDays(finalI);
                String dateString = localDateTime.format(format);
                dates.add(dateString);
                countDownLatch.countDown();
            });
        }
        //堵塞,直到countDown数量为0
        countDownLatch.await();
        System.out.println(dates.size());//100
    }
}

 

标签:线程,private,SimpleDateFormat,static,new,calendar,多线程
From: https://www.cnblogs.com/badaoliumangqizhi/p/17458754.html

相关文章

  • 多线程同步AutoResetEvent 和ManualResetEvent
         ......
  • Java开发手册中为什么要求SimpleDateFormat时用y表示年,而不能用Y
    场景SimpleDateFormat是Java提供的一个格式化和解析日期的工具类。它允许进行格式化(日期->文本)、解析(文本->日期)和规范化。SimpleDateFormat使得可以选择任何用户定义的日期-时间格式的模式。在Java中,可以使用SimpleDateFormat的format方法,将一个Date类型转化为Stri......
  • python爬虫爬取快手视频多线程下载功能【fd的使用】
    环境:python2.7+win10工具:fiddlerpostman安卓模拟器首先,打开fiddler,fiddler作为http/https抓包神器,这里就不多介绍。配置允许https 配置允许远程连接也就是打开http代理 电脑ip:192.168.1.110然后确保手机和电脑是在一个局域网下,可以通信。由于我这边没有安卓......
  • C语言多线程爬虫代码示例
    使用C语言编写多线程爬虫能够同时处理多条数据,提高了爬虫的并发度和效率。在编写多线程爬虫时仍需要注意线程安全性和错误处理机制,并根据系统资源和目标网站的特点调整线程数和优化并发策略,以提高程序效率和稳定性。以下是一个使用C语言多线程编写的简单爬虫示例,实现了并发爬取多......
  • Java:从单线程计数器到多线程数据同步synchronized和原子类Atomic
    (目录)使用单线程单线程修改计数器的值,没有发生问题,每次运行结果都是10000,不过程序耗时较长packagecom.example;/***计数器*/classCounter{privatestaticlongcount;publicstaticlonggetCount(){returncount;}publicstaticv......
  • 类型转换static_cast<type>(value)
    来自CHATGPT的回答  static_cast<float>(value)和(float)value实际上是完成相同的类型转换,即将value的类型转换为float类型。两者的区别在于语法和一些特定的使用情境。语法:static_cast<float>(value)是使用C++中的static_cast运算符进行类型转换的方式,而(fl......
  • 必知必会:多线程
    1.线程的6种状态(1)New:初始状态,线程被创建,但是还没调用start方法。(2)Running:就绪状态和运行状态,统称为运行状态(3)Blocked:阻塞状态(4)Waiting:等待状态,需要等待其他线程做出特定的动作(通知或中断)。(5)Time-Waiting:超时等待状态,表示可以在指定的时间自行返回。(6)Terminated:终止状态,表示当前......
  • 记录一次QT5下多线程使用Qxlsx操作写EXCEL表文件问题
    问题表述:一个主线程和两个子线程,两个子线程进行写EXCEL表格文件,线程1写demo_1.xlsx,线程2写demo_2.xlsx,运行一段时间后程序异常退出?。代码如下://两个线程代码一样,只是写入的文件名不同QXlsx::Documentdocument("demo_x.xlsx");introwLen=document.dimension()......
  • 【python】多线程
     在Python3中,通过threading模块提供线程的功能。原来的thread模块已废弃。但是threading模块中有个Thread类(大写的T,类名),是模块中最主要的线程类,一定要分清楚了,千万不要搞混了。threading模块提供了一些比较实用的方法或者属性,例如:方法与属性描述current_thread()返......
  • 集合,多线程,面向对象,方法覆盖
    集合:“父亲”collection"儿子":list:有序,有下标,查set:无序,无下标,修改(底层:内存存储方式)列表,不方便map:键值对,key(标号)-value(真实的值,储放的是分散的物品,小型数据库)多线程:进程(可以索取计算机运行资源)=多线程(不可以索取,只能进程的资源)oop面向对象=封装,继承,多态面向过程:按照......