Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
时间格式化 js_文件时间修改器,希望能够帮助你!!!。
SimpleDateFormat在多线程环境下存在线程安全问题。
错误使用SimpleDateFormat.parse()的代码如下:
import java.text.SimpleDateFormat; public class SimpleDateFormatTest { private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) { /** * SimpleDateFormat线程不安全,没有保证线程安全(没有加锁)的情况下,禁止使用全局SimpleDateFormat,否则报错 NumberFormatException * * private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); */ for (int i = 0; i < 20; ++i) { Thread thread = new Thread(() -> { try { // 错误写法会导致线程安全问题 System.out.println(Thread.currentThread().getName() + "--" + SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00")); } catch (Exception e) { e.printStackTrace(); } }, "Thread-" + i); thread.start(); } } }
报错:
查看源码中可以看到:SimpleDateFormat继承DateFormat类,SimpleDateFormat转换日期是通过继承自DateFormat类的Calendar对象来操作的,Calendar对象会被用来进行日期-时间计算,既被用于format方法也被用于parse方法。
SimpleDateFormat 的 parse(String source) 方法 会调用继承自父类的 DateFormat 的 parse(String source) 方法
DateFormat 的 parse(String source) 方法会调用SimpleDateFormat中重写的 parse(String text, ParsePosition pos) 方法,该方法中有个地方需要关注
SimpleDateFormat 中重写的 parse(String text, ParsePosition pos) 方法中调用了 establish(calendar) 这个方法:
该方法中调用了 Calendar 的 clear() 方法
可以发现整个过程中Calendar对象它并不是线程安全的,如果,a线程将calendar清空了,calendar 就没有新值了,恰好此时b线程刚好进入到parse方法用到了calendar对象,那就会产生线程安全问题了!
正常情况下:
非线程安全的流程:
方法1:每个线程都new一个SimpleDateFormat
import java.text.SimpleDateFormat; public class SimpleDateFormatTest { public static void main(String[] args) { for (int i = 0; i < 20; ++i) { Thread thread = new Thread(() -> { try { // 每个线程都new一个 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(Thread.currentThread().getName() + "--" + simpleDateFormat.parse("2020-06-01 11:35:00")); } catch (Exception e) { e.printStackTrace(); } }, "Thread-" + i); thread.start(); } } }
方式2:synchronized等方式加锁
public class SimpleDateFormatTest { private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args) { for (int i = 0; i < 20; ++i) { Thread thread = new Thread(() -> { try { synchronized (SIMPLE_DATE_FORMAT) { System.out.println(Thread.currentThread().getName() + "--" + SIMPLE_DATE_FORMAT.parse("2020-06-01 11:35:00")); } } catch (Exception e) { e.printStackTrace(); } }, "Thread-" + i); thread.start(); } } }
方式3:使用ThreadLocal 为每个线程创建一个独立变量
import java.text.DateFormat; import java.text.SimpleDateFormat; public class SimpleDateFormatTest { private static final ThreadLocal<DateFormat> SAFE_SIMPLE_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); public static void main(String[] args) { for (int i = 0; i < 20; ++i) { Thread thread = new Thread(() -> { try { System.out.println(Thread.currentThread().getName() + "--" + SAFE_SIMPLE_DATE_FORMAT.get().parse("2020-06-01 11:35:00")); } catch (Exception e) { e.printStackTrace(); } }, "Thread-" + i); thread.start(); } } }
ThreadLocal的详细使用细节见:
https://blog.csdn.net/QiuHaoqian/article/details/
import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class SimpleDateFormatTest { // 时间格式化对象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); public static void main(String[] args) throws InterruptedException { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 执行任务 threadPool.execute(new Runnable() { @Override public void run() { Date date = new Date(finalI * 1000); // 得到时间对象 formatAndPrint(date); // 执行时间格式化 } }); } threadPool.shutdown(); // 线程池执行完任务之后关闭 } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { String result = simpleDateFormat.format(date); // 执行格式化 System.out.println("时间:" + result); // 打印最终结果 } }
从上述结果可以看出,程序的打印结果竟然是有重复内容的,正确的情况应该是没有重复的时间才对。
为了找到问题所在,查看原因 SimpleDateFormat 中 format 方法的源码来排查一下问题,format 源码如下:
从上述源码可以看出,在执行任务 SimpleDateFormat.format() 方法时,会使用 calendar.setTime() 方法将输入的时间进行转换,那么我们想象一下这样的场景:
正常的情况下,程序的执行是这样的:
非线程安全的执行流程是这样的:
同样有三种解决方法
方法1:每个线程都new一个SimpleDateFormat
public class SimpleDateFormatTest { public static void main(String[] args) throws InterruptedException { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 执行任务 threadPool.execute(new Runnable() { @Override public void run() { // 得到时间对象 Date date = new Date(finalI * 1000); // 执行时间格式化 formatAndPrint(date); } }); } // 线程池执行完任务之后关闭 threadPool.shutdown(); } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { String result = new SimpleDateFormat("mm:ss").format(date); // 执行格式化 System.out.println("时间:" + result); // 打印最终结果 } }
方式2:synchronized等方式加锁
所有的线程必须排队执行某些业务才行,这样无形中就降低了程序的运行效率了
public class SimpleDateFormatTest { // 时间格式化对象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); public static void main(String[] args) throws InterruptedException { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( 10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)); for (int i = 0; i < 1000; i++) { int finalI = i; // 执行任务 threadPool.execute(new Runnable() { @Override public void run() { Date date = new Date(finalI * 1000); // 得到时间对象 formatAndPrint(date); // 执行时间格式化 } }); } // 线程池执行完任务之后关闭 threadPool.shutdown(); } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { // 执行格式化 String result = null; // 加锁 synchronized (SimpleDateFormatTest.class) { result = simpleDateFormat.format(date); } // 打印最终结果 System.out.println("时间:" + result); } }
方式3:使用ThreadLocal 为每个线程创建一个独立变量
public class SimpleDateFormatTest { // 创建 ThreadLocal 并设置默认值 private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss")); public static void main(String[] args) { // 创建线程池执行任务 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000)); // 执行任务 for (int i = 0; i < 1000; i++) { int finalI = i; // 执行任务 threadPool.execute(() -> { Date date = new Date(finalI * 1000); // 得到时间对象 formatAndPrint(date); // 执行时间格式化 }); } threadPool.shutdown(); // 线程池执行完任务之后关闭 } /** * 格式化并打印时间 */ private static void formatAndPrint(Date date) { String result = dateFormatThreadLocal.get().format(date); // 执行格式化 System.out.println("时间:" + result); // 打印最终结果 } }
文章来源:blog.csdn.net/QiuHaoqian/article/details/
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章