SimpleDateFormat类到底为啥不是线程安全的?(附六种解决方案,建议收藏)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上网络安全知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注网络安全)
img

正文

package io.binghe.concurrent.lab06;

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

/**

  • @author binghe

  • @version 1.0.0

  • @description 通过Synchronized锁解决SimpleDateFormat类的线程安全问题

*/

public class SimpleDateFormatTest03 {

//执行总次数

private static final int EXECUTE_COUNT = 1000;

//同时运行的线程数量

private static final int THREAD_COUNT = 20;

//SimpleDateFormat对象

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd”);

public static void main(String[] args) throws InterruptedException {

final Semaphore semaphore = new Semaphore(THREAD_COUNT);

final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);

ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < EXECUTE_COUNT; i++){

executorService.execute(() -> {

try {

semaphore.acquire();

try {

synchronized (simpleDateFormat){

simpleDateFormat.parse(“2020-01-01”);

}

} catch (ParseException e) {

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}catch (NumberFormatException e){

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}

semaphore.release();

} catch (InterruptedException e) {

System.out.println(“信号量发生错误”);

e.printStackTrace();

System.exit(1);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(“所有线程格式化日期成功”);

}

}

此时,解决问题的关键代码如下所示。

synchronized (simpleDateFormat){

simpleDateFormat.parse(“2020-01-01”);

}

运行程序,输出结果如下所示。

所有线程格式化日期成功

需要注意的是,虽然这种方式能够解决SimpleDateFormat类的线程安全问题,但是由于在程序的执行过程中,为SimpleDateFormat类对象加上了synchronized锁,导致同一时刻只能有一个线程执行parse(String)方法。此时,会影响程序的执行性能,在要求高并发的生产环境下,此种方式也是不太推荐使用的。

3.Lock锁方式

Lock锁方式与synchronized锁方式实现原理相同,都是在高并发下通过JVM的锁机制来保证程序的线程安全。通过Lock锁方式解决问题的代码如下所示。

package io.binghe.concurrent.lab06;

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

/**

  • @author binghe

  • @version 1.0.0

  • @description 通过Lock锁解决SimpleDateFormat类的线程安全问题

*/

public class SimpleDateFormatTest04 {

//执行总次数

private static final int EXECUTE_COUNT = 1000;

//同时运行的线程数量

private static final int THREAD_COUNT = 20;

//SimpleDateFormat对象

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(“yyyy-MM-dd”);

//Lock对象

private static Lock lock = new ReentrantLock();

public static void main(String[] args) throws InterruptedException {

final Semaphore semaphore = new Semaphore(THREAD_COUNT);

final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);

ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < EXECUTE_COUNT; i++){

executorService.execute(() -> {

try {

semaphore.acquire();

try {

lock.lock();

simpleDateFormat.parse(“2020-01-01”);

} catch (ParseException e) {

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}catch (NumberFormatException e){

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}finally {

lock.unlock();

}

semaphore.release();

} catch (InterruptedException e) {

System.out.println(“信号量发生错误”);

e.printStackTrace();

System.exit(1);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(“所有线程格式化日期成功”);

}

}

通过代码可以得知,首先,定义了一个Lock类型的全局静态变量作为加锁和释放锁的句柄。然后在simpleDateFormat.parse(String)代码之前通过lock.lock()加锁。这里需要注意的一点是:为防止程序抛出异常而导致锁不能被释放,一定要将释放锁的操作放到finally代码块中,如下所示。

finally {

lock.unlock();

}

运行程序,输出结果如下所示。

所有线程格式化日期成功

此种方式同样会影响高并发场景下的性能,不太建议在高并发的生产环境使用。

4.ThreadLocal方式

使用ThreadLocal存储每个线程拥有的SimpleDateFormat对象的副本,能够有效的避免多线程造成的线程安全问题,使用ThreadLocal解决线程安全问题的代码如下所示。

package io.binghe.concurrent.lab06;

import java.text.DateFormat;

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

/**

  • @author binghe

  • @version 1.0.0

  • @description 通过ThreadLocal解决SimpleDateFormat类的线程安全问题

*/

public class SimpleDateFormatTest05 {

//执行总次数

private static final int EXECUTE_COUNT = 1000;

//同时运行的线程数量

private static final int THREAD_COUNT = 20;

private static ThreadLocal threadLocal = new ThreadLocal(){

@Override

protected DateFormat initialValue() {

return new SimpleDateFormat(“yyyy-MM-dd”);

}

};

public static void main(String[] args) throws InterruptedException {

final Semaphore semaphore = new Semaphore(THREAD_COUNT);

final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);

ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < EXECUTE_COUNT; i++){

executorService.execute(() -> {

try {

semaphore.acquire();

try {

threadLocal.get().parse(“2020-01-01”);

} catch (ParseException e) {

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}catch (NumberFormatException e){

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}

semaphore.release();

} catch (InterruptedException e) {

System.out.println(“信号量发生错误”);

e.printStackTrace();

System.exit(1);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(“所有线程格式化日期成功”);

}

}

通过代码可以得知,将每个线程使用的SimpleDateFormat副本保存在ThreadLocal中,各个线程在使用时互不干扰,从而解决了线程安全问题。

运行程序,输出结果如下所示。

所有线程格式化日期成功

此种方式运行效率比较高,推荐在高并发业务场景的生产环境使用。

另外,使用ThreadLocal也可以写成如下形式的代码,效果是一样的。

package io.binghe.concurrent.lab06;

import java.text.DateFormat;

import java.text.ParseException;

import java.text.SimpleDateFormat;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

/**

  • @author binghe

  • @version 1.0.0

  • @description 通过ThreadLocal解决SimpleDateFormat类的线程安全问题

*/

public class SimpleDateFormatTest06 {

//执行总次数

private static final int EXECUTE_COUNT = 1000;

//同时运行的线程数量

private static final int THREAD_COUNT = 20;

private static ThreadLocal threadLocal = new ThreadLocal();

private static DateFormat getDateFormat(){

DateFormat dateFormat = threadLocal.get();

if(dateFormat == null){

dateFormat = new SimpleDateFormat(“yyyy-MM-dd”);

threadLocal.set(dateFormat);

}

return dateFormat;

}

public static void main(String[] args) throws InterruptedException {

final Semaphore semaphore = new Semaphore(THREAD_COUNT);

final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);

ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < EXECUTE_COUNT; i++){

executorService.execute(() -> {

try {

semaphore.acquire();

try {

getDateFormat().parse(“2020-01-01”);

} catch (ParseException e) {

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}catch (NumberFormatException e){

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}

semaphore.release();

} catch (InterruptedException e) {

System.out.println(“信号量发生错误”);

e.printStackTrace();

System.exit(1);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(“所有线程格式化日期成功”);

}

}

5.DateTimeFormatter方式

DateTimeFormatter是Java8提供的新的日期时间API中的类,DateTimeFormatter类是线程安全的,可以在高并发场景下直接使用DateTimeFormatter类来处理日期的格式化操作。代码如下所示。

package io.binghe.concurrent.lab06;

import java.time.LocalDate;

import java.time.format.DateTimeFormatter;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

/**

  • @author binghe

  • @version 1.0.0

  • @description 通过DateTimeFormatter类解决线程安全问题

*/

public class SimpleDateFormatTest07 {

//执行总次数

private static final int EXECUTE_COUNT = 1000;

//同时运行的线程数量

private static final int THREAD_COUNT = 20;

private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern(“yyyy-MM-dd”);

public static void main(String[] args) throws InterruptedException {

final Semaphore semaphore = new Semaphore(THREAD_COUNT);

final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);

ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < EXECUTE_COUNT; i++){

executorService.execute(() -> {

try {

semaphore.acquire();

try {

LocalDate.parse(“2020-01-01”, formatter);

}catch (Exception e){

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}

semaphore.release();

} catch (InterruptedException e) {

System.out.println(“信号量发生错误”);

e.printStackTrace();

System.exit(1);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(“所有线程格式化日期成功”);

}

}

可以看到,DateTimeFormatter类是线程安全的,可以在高并发场景下直接使用DateTimeFormatter类来处理日期的格式化操作。

运行程序,输出结果如下所示。

所有线程格式化日期成功

使用DateTimeFormatter类来处理日期的格式化操作运行效率比较高,推荐在高并发业务场景的生产环境使用

6.joda-time方式

joda-time是第三方处理日期时间格式化的类库,是线程安全的。如果使用joda-time来处理日期和时间的格式化,则需要引入第三方类库。这里,以Maven为例,如下所示引入joda-time库。

joda-time

joda-time

2.9.9

引入joda-time库后,实现的程序代码如下所示。

package io.binghe.concurrent.lab06;

import org.joda.time.DateTime;

import org.joda.time.format.DateTimeFormat;

import org.joda.time.format.DateTimeFormatter;

import java.util.concurrent.CountDownLatch;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

/**

  • @author binghe

  • @version 1.0.0

  • @description 通过DateTimeFormatter类解决线程安全问题

*/

public class SimpleDateFormatTest08 {

//执行总次数

private static final int EXECUTE_COUNT = 1000;

//同时运行的线程数量

private static final int THREAD_COUNT = 20;

private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(“yyyy-MM-dd”);

public static void main(String[] args) throws InterruptedException {

final Semaphore semaphore = new Semaphore(THREAD_COUNT);

final CountDownLatch countDownLatch = new CountDownLatch(EXECUTE_COUNT);

ExecutorService executorService = Executors.newCachedThreadPool();

for (int i = 0; i < EXECUTE_COUNT; i++){

executorService.execute(() -> {

try {

semaphore.acquire();

try {

DateTime.parse(“2020-01-01”, dateTimeFormatter).toDate();

}catch (Exception e){

System.out.println(“线程:” + Thread.currentThread().getName() + " 格式化日期失败");

e.printStackTrace();

System.exit(1);

}

semaphore.release();

} catch (InterruptedException e) {

System.out.println(“信号量发生错误”);

e.printStackTrace();

System.exit(1);

}

countDownLatch.countDown();

});

}

countDownLatch.await();

executorService.shutdown();

System.out.println(“所有线程格式化日期成功”);

}

}

这里,需要注意的是:DateTime类是org.joda.time包下的类,DateTimeFormat类和DateTimeFormatter类都是org.joda.time.format包下的类,如下所示。

import org.joda.time.DateTime;

import org.joda.time.format.DateTimeFormat;

import org.joda.time.format.DateTimeFormatter;

运行程序,输出结果如下所示。

所有线程格式化日期成功

使用joda-time库来处理日期的格式化操作运行效率比较高,推荐在高并发业务场景的生产环境使用。

解决SimpleDateFormat类的线程安全问题的方案总结

综上所示:在解决解决SimpleDateFormat类的线程安全问题的几种方案中,局部变量法由于线程每次执行格式化时间时,都会创建SimpleDateFormat类的对象,这会导致创建大量的SimpleDateFormat对象,浪费运行空间和消耗服务器的性能,因为JVM创建和销毁对象是要耗费性能的。所以,不推荐在高并发要求的生产环境使用

synchronized锁方式和Lock锁方式在处理问题的本质上是一致的,通过加锁的方式,使同一时刻只能有一个线程执行格式化日期和时间的操作。这种方式虽然减少了SimpleDateFormat对象的创建,但是由于同步锁的存在,导致性能下降,所以,不推荐在高并发要求的生产环境使用。

ThreadLocal通过保存各个线程的SimpleDateFormat类对象的副本,使每个线程在运行时,各自使用自身绑定的SimpleDateFormat对象,互不干扰,执行性能比较高,推荐在高并发的生产环境使用。

DateTimeFormatter是Java 8中提供的处理日期和时间的类,DateTimeFormatter类本身就是线程安全的,经压测,DateTimeFormatter类处理日期和时间的性能效果还不错(后文单独写一篇关于高并发下性能压测的文章)。所以,推荐在高并发场景下的生产环境使用。

joda-time是第三方处理日期和时间的类库,线程安全,性能经过高并发的考验,推荐在高并发场景下的生产环境使用

写在最后


如果你想进大厂,想升职加薪,或者对自己现有的工作比较迷茫,都可以私信我交流,希望我的一些经历能够帮助到大家~~

推荐阅读:

本人从事网路安全工作12年,曾在2个大厂工作过,安全服务、售后服务、售前、攻防比赛、安全讲师、销售经理等职位都做过,对这个行业了解比较全面。

最近遍览了各种网络安全类的文章,内容参差不齐,其中不伐有大佬倾力教学,也有各种不良机构浑水摸鱼,在收到几条私信,发现大家对一套完整的系统的网络安全从学习路线到学习资料,甚至是工具有着不小的需求。

最后,我将这部分内容融会贯通成了一套282G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

2G的网络安全资料包,所有类目条理清晰,知识点层层递进,需要的小伙伴可以点击下方小卡片领取哦!下面就开始进入正题,如何从一个萌新一步一步进入网络安全行业。

学习路线图

其中最为瞩目也是最为基础的就是网络安全学习路线图,这里我给大家分享一份打磨了3个月,已经更新到4.0版本的网络安全学习路线图。

相比起繁琐的文字,还是生动的视频教程更加适合零基础的同学们学习,这里也是整理了一份与上述学习路线一一对应的网络安全视频教程。

网络安全工具箱

当然,当你入门之后,仅仅是视频教程已经不能满足你的需求了,你肯定需要学习各种工具的使用以及大量的实战项目,这里也分享一份我自己整理的网络安全入门工具以及使用教程和实战。

项目实战

最后就是项目实战,这里带来的是SRC资料&HW资料,毕竟实战是检验真理的唯一标准嘛~

面试题

归根结底,我们的最终目的都是为了就业,所以这份结合了多位朋友的亲身经验打磨的面试题合集你绝对不能错过!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注网络安全)
[外链图片转存中…(img-tp47f4M4-1713174216872)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值