借助SimpleDateFormat来谈谈java里的多线程不安全()

  本篇文章为你整理了借助SimpleDateFormat来谈谈java里的多线程不安全()的详细内容,包含有 借助SimpleDateFormat来谈谈java里的多线程不安全,希望能帮助你了解 借助SimpleDateFormat来谈谈java里的多线程不安全。

   所谓线程不安全,指的是并发场景下,多个线程同时操作同一资源时,产生的结果与预期不一致的情况。这里的操作,指的是写操作。读操作是不会出现线程不安全的。

  
SimpleDateFormat问题

  程序里有一段逻辑涉及到将时间字符串转换成Date对象。昨夜系统发版,在一个批处理定时任务频繁访问这段代码时,由于使用了静态全局的线程不安全的java.text.SimpleDateFormat对象(@link java.text.SimpleDateFormat#parse),致使多线程操作出现转换异常。

  

  简化场景,我们看下面的代码。这段代码通过多线程使用同一个SimpleDateFormat对象的parse方法, 多次执行代码来测试,可以看到会出现两种预想不到的现象,要么出现不正确的时间解析结果,要么抛出message各异的NumberFormatException异常。

  

  

package jstudy.dateformat;

 

  import java.text.ParseException;

  import java.text.SimpleDateFormat;

  import java.util.concurrent.ExecutorService;

  import java.util.concurrent.Executors;

  public class SimpleDateFormatTest {

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

   ExecutorService threadPool = Executors.newFixedThreadPool(20);

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

   String startDate = "2022-01-07 15:40:15";

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

   threadPool.execute(() - {

   for (int j = 0; j j++) {

   try {

   // new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(startDate); //使用局部变量可以避免出现线程不安全

   System.out.println(datetimeFormat.parse(startDate));

   } catch (ParseException e) {

   e.printStackTrace();

  }

 

  如下是NumberFormatException异常信息:一种message是multiple points; 一种message是“for input string ***”;还有一种message是empty String。

  

Exception in thread "pool-1-thread-26" java.lang.NumberFormatException: multiple points

 

  at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)

  at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)

  at java.lang.Double.parseDouble(Double.java:538)

  at java.text.DigitList.getDouble(DigitList.java:169)

  at java.text.DecimalFormat.parse(DecimalFormat.java:2056)

  at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)

  at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

  at java.text.DateFormat.parse(DateFormat.java:364)

  at jstudy.dateformat.TestMain.lambda$testNull$0(TestMain.java:24)

  ===================================================================

  java.lang.NumberFormatException: For input string: ""

  at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)

  at java.lang.Long.parseLong(Long.java:601)

  at java.lang.Long.parseLong(Long.java:631)

  at java.text.DigitList.getLong(DigitList.java:195)

  at java.text.DecimalFormat.parse(DecimalFormat.java:2051)

  at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)

  at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

  at java.text.DateFormat.parse(DateFormat.java:364)

  at jstudy.dateformat.TestMain.lambda$testNull$0(TestMain.java:24)

  ===================================================================

  java.lang.NumberFormatException: For input string: "E.420222022E4"

  at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)

  at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)

  at java.lang.Double.parseDouble(Double.java:538)

  at java.text.DigitList.getDouble(DigitList.java:169)

  at java.text.DecimalFormat.parse(DecimalFormat.java:2056)

  at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)

  at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

  at java.text.DateFormat.parse(DateFormat.java:364)

  at jstudy.dateformat.TestMain.lambda$testNull$0(TestMain.java:24)

  ===================================================================

  java.lang.NumberFormatException: empty String

  at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)

  at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)

  at java.lang.Double.parseDouble(Double.java:538)

  at java.text.DigitList.getDouble(DigitList.java:169)

  at java.text.DecimalFormat.parse(DecimalFormat.java:2056)

  at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)

  at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)

  at java.text.DateFormat.parse(DateFormat.java:364)

  Tue Mar 07 15:40:15 CST 2023

  Fri Jan 07 15:40:15 CST 2022

  Wed Jan 07 15:15:15 CST 1

  at jstudy.dateformat.TestMain.lambda$testNull$0(TestMain.java:24)

 

  View Code

  

  线程不安全,即多线程不安全

  所谓线程不安全,指的是并发场景下,多个线程同时操作同一资源时,产生的结果与预期不一致的情况。这里的操作,指的是写操作。读操作是不会出现线程不安全的。与线程不安全相对的是线程安全,多线程同时操作同一资源,与单个线程的操作结果相同,就是线程安全。

  上文提到了多线程下才会存在线程安全与线程不安全的情况。所以,线程安全可以称为多线程安全,线程不安全也称为多线程不安全,这样更容易理解。

  如何保证多线程下的线程安全呢?

  有同学会说,自然是对共用资源进行加锁了。上面提到的java.text.SimpleDateFormat类内部就是未对资源上锁,导致当同时有多个线程使用同一个SimpleDateFormat对象时,因为线程不安全而出现计算错误或NumberFormatException。

  方法很多,下面章节赘述。

  多线程不安全是怎么出现的?

  1).首先是并发场景下,并发场景下会同时存在多个线程(因为单线程也就不用谈线程不安全了)。

  2).其次,是程序里有全局变量:类内部的全局变量,或工具类里public的静态全局变量。另一种情况是,程序里操作涉及到全局资源,如数据库表、缓存数据、磁盘文件。

  3).然后,代码里引用了2)中提到的全局变量或全局资源。

  4).碰巧,2)中提到的全局变量的class内部没有采用加锁机制;或者,程序在使用2)中提到的全局变量或全局资源时也未做加锁处理。

  如何规避多线程不安全(如何保证多线程安全)

  问题即答案!

  a)不共用全局变量。各线程各自new自己的对象,使用局部变量。

  b)共用全局变量的话,可以借助ThreadLocal T 来实现每个线程自己的副本。

  b)线程同步。

  b1)共用全局变量时,使用加锁处理来保证操作的同步性,即,同时只允许一个线程来操作共享资源。可以借助悲观的synchronized锁,或乐观的可重入锁j.u.c.locks.ReentrantLock。

  
synchronized修饰表示代码块同步,synchronized修饰方法表示方法同步。

  ReentrantLock实现j.u.c.locks.Lock接口,它更灵活,分离了读操作和写操作,即允许同时多个线程读和只有一个线程写。

  
b2)对于数据库数据的操作,除了b1)提到的程序加锁外,还可以借助数据库里的悲观锁,如mysql的for update。当然,这种悲观锁会降低程序执行效率,所以,也可以通过程序实现乐观锁来控制,常见的乐观锁是使用版本号、状态锁。

  b3)对于分布式系统来说,要使用分布式锁来保证不同进程的线程同步,分布式锁可以使用Redis自旋等待来实现,或使用Redisson分布式锁。

  c)改用线程安全的类对象,即使用不可变的类对象。例如,使用java.time.format.DateTimeFormatter替代java.text.SimpleDateFormat;使用j.u.c.atomic.AtomicInteger替代java.lang.Integer;使用java.lang.StringBuffer替代java.lang.StringBuilder。

  SimpleDateFormat类的javadoc里,如下文字写的真清晰! Date formats(SimpleDateFormat继承DateFormat类) are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized.

  DateTimeFormatter的javadoc上写的很直白:This class is immutable and thread-safe.(这个类是不可变的,并且是线程安全的)。

  同样,StringBuilder类里也明确写着:StringBuilder是一个可变的(mutable)字符序列。Instances of {@code StringBuilder} are not safe for use by multiple threads. If such synchronization is required then it is recommended that {@link java.lang.StringBuffer} be used.---StringBuilder实例是多线程不安全的,如果在需要保证线程同步的场景下就要使用StringBuffer来替代。 当然了,我们通常不会在代码里放一个全局的StringBuffer来操作字符串拼接。所以,在方法内部的话,还是推荐用StringBuilder,因为StringBuilder更faster。

  ConcurrentHashMap是线程安全类,即ConcurrentHashMap的方法都提供了同步机制(ConcurrentHashMap取代了Java5之前版本的Hashtable);HashMap不提供同步机制,不是线程安全类。

  
当看到一些不好的代码时,会发现我还算优秀;当看到优秀的代码时,也才意识到持续学习的重要!--buguge
本文来自,转载请注明原文链接:https:///buguge/p/15776757.html

  以上就是借助SimpleDateFormat来谈谈java里的多线程不安全()的详细内容,想要了解更多 借助SimpleDateFormat来谈谈java里的多线程不安全的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: