精华内容
下载资源
问答
  • 这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题? 如果我们想要使用SimpleDateFormat类来对一个时间或者日期进行格式化,并且还要使用多线程来操作,即使用多线程同时对一个时间或者日期进行格式化...

    回顾一下以前的那套日期时间API,你就能发现它是线程不安全的,是可变的。这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?

    如果我们想要使用SimpleDateFormat类来对一个时间或者日期进行格式化,并且还要使用多线程来操作,即使用多线程同时对一个时间或者日期进行格式化,那么该咋办呢?我们可以创建一个线程池,然后分10次去访问定义好的一个任务(该任务就是专门用于格式化一个时间或者日期的),都来解析某个时间或者日期。

    package com.meimeixia.java8;
    
    import java.text.SimpleDateFormat;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    /**
     * 这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?
     * @author liayun
     *
     */
    public class TestSimpleDateFormat {
    	
        public static void main(String[] args) throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            
            //定义好如下一个任务(task),该任务就是专门用于格式化一个时间或者日期的
            Callable<Date> task = new Callable<Date>() {
    
                @Override
                public Date call() throws Exception {
                    return sdf.parse("20191207");
                }
                
            };
            
            //创建一个长度为10的线程池
            ExecutorService pool = Executors.newFixedThreadPool(10);
            
            List<Future<Date>> results = new ArrayList<Future<Date>>();
            
            //分10次去访问以上定义好的任务(task),然后它就会返回一个结果(叫Future),结果我都给它放在上面的集合里面
            for (int i = 0; i < 10; i++) {
                results.add(pool.submit(task));
            }
            
            for (Future<Date> future : results) {
                System.out.println(future.get());
            }
        }
    
    }
    

    运行以上程序,会发现报了如下错误,日期已经格式化不下去了。这说明已经存在多线程安全问题了,也就是说SimpleDateFormat类或者传统的时间日期API均存在多线程安全问题。
    在这里插入图片描述
    抛开Java 8来说的话,要是以前,这个问题该怎么解决呢?要想解决这个多线程安全问题,就得上锁。这就引出了一系列的问题,对什么上锁?怎么上锁?上什么锁?一句话搞定,使用ThreadLocal类对以上程序中的sdf变量上锁,说白了,ThreadLocal类就可以锁这个变量。那怎么怎么上锁呢?

    package com.meimeixia.java8;
    
    import java.text.DateFormat;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class DateFormatThreadLocal {
    	
    	/*
    	 * ThreadLocal说白了,是不是就可以锁这个变量啊?
    	 */
    	private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
    		//ThreadLocal里面有一个供子类继承的方法,即initialValue()
    		protected DateFormat initialValue() {
    			return new SimpleDateFormat("yyyyMMdd");
    		}
    	};
    	
    	public static Date convert(String source) throws ParseException {
    		return df.get().parse(source);
    	}
    
    }
    

    然后,使用被ThreadLocal锁上的SimpleDateFormat来格式化一个时间或者日期。

    package com.meimeixia.java8;
    
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    /**
     * 这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?
     * @author liayun
     *
     */
    public class TestSimpleDateFormat {
    	
    	public static void main(String[] args) throws Exception {
    		//定义好如下一个任务(task),该任务就是专门用于格式化一个时间或者日期的
    		Callable<Date> task = new Callable<Date>() {
    
    			@Override
    			public Date call() throws Exception {
    				//这次的SimpleDateFormat是不是被ThreadLocal给锁上了啊!
    				return DateFormatThreadLocal.convert("20191205");
    			}
    			
    		};
    		
    		//创建一个长度为10的线程池
    		ExecutorService pool = Executors.newFixedThreadPool(10);
    		
    		List<Future<Date>> results = new ArrayList<Future<Date>>();
    		
    		//分10次去访问以上定义好的任务(task),然后它就会返回一个结果(叫Future),结果我都给它放在上面的集合里面
    		for (int i = 0; i < 10; i++) {
    			results.add(pool.submit(task));
    		}
    		
    		for (Future<Date> future : results) {
    			System.out.println(future.get());
    		}
    		
    		//运行以上程序,发现程序没停下来!为什么啊?池忘记关了!
    		pool.shutdown();
    	}
    	
    }
    

    此时,运行以上程序,你便能在Eclipse控制台看到如下打印内容了。
    在这里插入图片描述
    这说明通过上锁解决了传统日期时间格式化的多线程安全问题。

    而现在,使用Java 8中这套全新的日期时间API之后,就没有什么多线程安全问题了,因为你不管做什么样的改变,它都会给你产生一个全新的实例,所以说它是线程安全的,这样就解决了多线程的安全问题。

    package com.meimeixia.java8;
    
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    /**
     * 这里就以传统日期时间格式化为例,看看它存在什么多线程安全问题?
     * @author liayun
     *
     */
    public class TestSimpleDateFormat {
    	
    	public static void main(String[] args) throws Exception {              
    		// DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;//常用的格式
    		DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");//指定自己指定的格式
    		
    		//现在,Java 8以后,这套全新的日期时间API(例如LocalDate、LocalTime、LocalDateTime类)中类的实例完全就是不可变的对象,这就解决了多线程安全问题
    		Callable<LocalDate> task = new Callable<LocalDate>() {
    
    			@Override
    			public LocalDate call() throws Exception {
    				//使用Java 8中这套全新的日期时间API之后,就没有什么多线程安全问题了,因为你不管做什么样的改变,它都会给你产生一个全新的实例,所以说它是线程安全的,这样就解决了多线程的安全问题。
    				return LocalDate.parse("20191203", dtf);
    			}
    			
    		};
    		
    		//创建一个长度为10的线程池
    		ExecutorService pool = Executors.newFixedThreadPool(10);
    		
    		List<Future<LocalDate>> results = new ArrayList<Future<LocalDate>>();
    		
    		//分10次去访问以上定义好的任务(task),然后它就会返回一个结果(叫Future),结果我都给它放在上面的集合里面
    		for (int i = 0; i < 10; i++) {
    			results.add(pool.submit(task));
    		}
    		
    		for (Future<LocalDate> future : results) {
    			System.out.println(future.get());
    		}
    		
    		//运行以上程序,发现程序没停下来!为什么啊?池忘记关了!
    		pool.shutdown();
    	}
    	
    }
    

    此时,运行以上程序,你便能在Eclipse控制台看到如下打印内容了。
    在这里插入图片描述
    这就说明了Java 8中这套全新的日期时间API是线程安全的。

    展开全文
  • 多线程安全时间格式化操作

    千次阅读 2018-04-18 17:42:25
    SimpleDateFormat是一个非线程安全的实现。从以下代码可以体现。 1.packageorg.saxing; 2. 3.importjava.text.ParseException; 4.importjava.text.SimpleDateFormat; 5.importjava.util.Date; 6.import...

    SimpleDateFormat是一个非线程安全的实现。从以下代码可以体现。

     

    1.  package org.saxing;  

    2.    

    3.  import java.text.ParseException;  

    4.  import java.text.SimpleDateFormat;  

    5.  import java.util.Date;  

    6.  import java.util.HashMap;  

    7.  import java.util.Map;  

    8.  import java.util.concurrent.*;  

    9.    

    10. /** 

    11.  * SimpleDateFormat问题 

    12.  * 

    13.  * Created by saxing on 2018/3/19. 

    14.  */  

    15. public class DateUtil {  

    16.   

    17.     private static Map<String, SimpleDateFormat> ps = new HashMap<>();  

    18.   

    19.     private static SimpleDateFormat getSdf1(String pattern){  

    20.         SimpleDateFormat s = ps.get(pattern);  

    21.         if (s == null){  

    22.             s = new SimpleDateFormat(pattern);  

    23.             ps.put(pattern, s);  

    24.         }  

    25.         return s;  

    26.     }  

    27.   

    28.     public static String format(Date date, String pattern){  

    29.         return getSdf1(pattern).format(date);  

    30.     }  

    31.   

    32.     public static Date parse(String dateString, String pattern) throws ParseException {  

    33.         return getSdf1(pattern).parse(dateString);  

    34.     }  

    35.   

    36.     public static void main(String[] args) throws ParseException {  

    37.         String dateStr1 = "2018-03-19 23:29:34";  

    38.         String dateStr2 = "2017-03-19";  

    39.         String pattern1 = "yyyy-MM-dd HH:mm:ss";  

    40.         String pattern2 = "yyyy-MM-dd";  

    41.         int fixedNum = 4;  

    42.         int threadNum = 9999;  

    43.   

    44.         Runnable runnable = () -> {  

    45.             try {  

    46.                 String resStr1 = DateUtil.format(DateUtil.parse(dateStr1, pattern1), pattern1);  

    47.                 if (!dateStr1.equals(resStr1)){  

    48.                     System.out.println("error\t resStr1: " + resStr1 + " dateStr1: " + dateStr1);  

    49.                 }  

    50.                 String resStr2 = DateUtil.format(DateUtil.parse(dateStr2, pattern2), pattern2);  

    51.                 if (!dateStr2.equals(resStr2)){  

    52.                     System.out.println("error\t resStr1: " + resStr2 + " dateStr2: " + dateStr2);  

    53.                 }  

    54.             } catch (ParseException e) {  

    55.                 e.printStackTrace();  

    56.             }  

    57.         };  

    58.   

    59.         ExecutorService cachedPool = Executors.newCachedThreadPool();  

    60.         ExecutorService fixedPool = Executors.newFixedThreadPool(fixedNum);  

    61.         fixedPool.execute(() -> {  

    62.             for (int i = 0; i < threadNum; i++){  

    63.                 cachedPool.execute(runnable);  

    64.             }  

    65.         });  

    66.     }  

    67. }  

     

    以上代码多次运行, 便会出现以下结果:

     

    上述代码有如下几个问题:

    1. SimpleDateFormat的format()、parse()为非线程安全,且没有做限制;

    2. HashMap为非线程安全,且没有做限制;

     

     

    SimpleDateFormat的format()、parse()为非线程安全,且没有做限制;

    先看下SimpleDateFormat的源码:

     

    1.  |- java.lang.Object  

    2.     |- java.text.Format  

    3.         |- java.text.DateFormat  

    4.            |- java.text.SimpleDateFormat  

     

    图1

    从中可分析出,SimpleDateFormat的操作中有对calendar对象进行清除和获取操作。那么问题来了,如果在A线程set(time)之后,B线程进行了clear()操作,那A线程进行getTime()时,便会获取到“Thu Jan 01 00:00:00 CST 1970”,产生错误。

    图2

     

    解决此问题有如下:

    方案一: 使用局部变量

    每次都new一个SimpleDateFormat对象。

    代码更改如下:

    1.  private static SimpleDateFormat getSdf(String pattern){  

    2.      return new SimpleDateFormat(pattern);  

    3.  }  

     

     

    图3

    这种方法虽然简单,但是会引发频繁的GC。

    图4

    方案二:使用同步代码块

     

    1.  private static final Object LOCK_OBJ = new Object();  

    2.    

    3.  private static final Map<String, SimpleDateFormat> PS = new HashMap<>();  

    4.    

    5.  private static SimpleDateFormat getSdf1(String pattern){  

    6.      SimpleDateFormat s = PS.get(pattern);  

    7.      if (s == null){  

    8.          s = new SimpleDateFormat(pattern);  

    9.          PS.put(pattern, s);  

    10.     }  

    11.     return s;  

    12. }  

    13.   

    14. public static String format(Date date, String pattern){  

    15.     synchronized (LOCK_OBJ){  

    16.         String str = getSdf1(pattern).format(date);  

    17.         return str;  

    18.     }  

    19. }  

    20.   

    21. public static Date parse(String dateString, String pattern) throws ParseException {  

    22.     synchronized (LOCK_OBJ){  

    23.         Date date = getSdf1(pattern).parse(dateString);  

    24.         return date;  

    25.     }  

    26. }  

     

    经测试99万线程并发没有产生错误。同时看一下性能:

     

    图5

     

    对比图4可以看出, gc次数和gc时间都有非常明显的下降。

    Synchronized原理很简单,简单分析一下代码:

    图6

    锁对象有一个监视器锁(monitor),当一个线程执行monitorenter时,便会获取锁,同时此监视器内部计数器加1,当同一个进程多次进入此monitor时,monitor内计数器会多次加1。此时其他线程无法再获取此monitor;当monitorexit时,监视器内部计数器减1,直到减至0,其他被此monitor阻塞线程才可以竞争获取此锁。图中有两个monitorexit, 第二个monitorexit是为了确认锁已退出。

     

    方案三:使用ThreadLocal(弃)

     

    先说明此方案不推荐使用,或者是我没有找到好方法。仅介绍一下。不喜欢看这节的可以跳过。

           虽然方案二的Synchronized解决了并发线程安全问题,但是效率并不高。

     

    图7

    这时候可以了解ThreadLocal。以下为ThreadLocal在源码里的定义。

    This class provides thread-local variables.  These variables differ from their normalcounterparts in that each thread that accesses one (via its {@code get} or{@code set} method) has its own, independently initialized copy of thevariable.  {@code ThreadLocal} instancesare typically private static fields in classes that wish to associate statewith a thread (e.g.,  a user ID orTransaction ID).

        大意为ThreadLocal是为线程提供局部变量,每个变量都有自己的副本。这种变量在多线程下访问时,能保证各个线程里的变量(通过get()/set()访问)独立于其他线程里的变量。ThreadLocal通常声明为private static类型,用于关联一个线程。

    1.  private static ThreadLocal<Map<String, SimpleDateFormat>> threadLocal = new ThreadLocal(){  

    2.      @Override  

    3.      protected Object initialValue() {  

    4.          return new HashMap<>();  

    5.      }  

    6.  };  

    7.    

    8.  private static SimpleDateFormat getSdf1(String pattern){  

    9.      Map<String, SimpleDateFormat> map = threadLocal.get();  

    10.     SimpleDateFormat df = map.get(pattern);  

    11.     if (df == null){  

    12.         df = new SimpleDateFormat(pattern);  

    13.         map.put(pattern, df);  

    14.         threadLocal.set(map);  

    15.     }  

    16.     return df;  

    17. }  

    18.   

    19. public static String format(Date date, String pattern){  

    20.     String str = getSdf1(pattern).format(date);  

    21.     return str;  

    22. }  

    23.   

    24. public static Date parse(String dateString, String pattern) throws ParseException {  

    25.     Date date = getSdf1(pattern).parse(dateString);  

    26.     return date;  

    27. }  

    28.   

     

    经过99万线程并发测试,没有错误。

    同时性能方面的测试和图7代码一样,但是结果如下:

    图8

    运算时间结果相差很大,明显使用了ThreadLocal性能卓越的多。当然简单的SimpleDateFormat一般也不会引起性能瓶颈,所以这种用法是否采用并不太重要。

           然而代码还是有问题的——ThreadLocal用法问题。前文定义里说明了ThreadLocal是为线程提供局部变量的,那么set的也是局部变量,无法让下一个线程取出上一个线程已经put入map的变量。那该怎么解决?答案是把局部变量变成全局变量。代码做如下演化:

    1.  private static final Map<String, ThreadLocal<SimpleDateFormat>> SM = new HashMap<>();  

    2.    

    3.  private static SimpleDateFormat getSdf1(String pattern){  

    4.      ThreadLocal<SimpleDateFormat> tl = SM.get(pattern);  

    5.      if (tl == null){  

    6.          tl = ThreadLocal.withInitial(() -> new SimpleDateFormat(pattern));  

    7.          SM.put(pattern, tl);  

    8.      }  

    9.      return tl.get();  

    10. }  


    代码还是有问题,这时候就回到本文开头第二点问题。HashMap.class是非线程安全的。当多线程并发时,容易出现一些问题。通过以下代码体现:

    1.  public class TestHashMap {  

    2.    

    3.      public static Map<Integer, Integer> map = new HashMap<>();  

    4.      private static final int THREAD_NUM = 1000;  

    5.    

    6.      public static void main(String[] args) {  

    7.          ExecutorService fixedPool = Executors.newFixedThreadPool(4);  

    8.          fixedPool.execute(() -> {  

    9.              for (int i = 1; i < THREAD_NUM; i++) {  

    10.                 int temp = i;  

    11.                 new Thread(() -> {  

    12.                     map.put(temp, temp);  

    13.                 }).start();  

    14.             }  

    15.             System.out.println("Insert Over.");  

    16.             try {  

    17.                 Thread.sleep(5000);  

    18.             } catch (InterruptedException e) {  

    19.                 e.printStackTrace();  

    20.             }  

    21.             readMap();  

    22.         });  

    23.     }  

    24.   

    25.     private static void readMap(){  

    26.         for (int i = 1; i < THREAD_NUM; i++) {  

    27.             Integer val = map.get(i);  

    28.             if (val == null){  

    29.                 System.out.println(i + ": lost data");  

    30.             }  

    31.         }  

    32.         System.out.println("read over");  

    33.     }  

    34. }  

    结果为:

    1.  Insert Over.  

    2.  117: lost data  

    3.  121: lost data  

    4.  123: lost data  

    5.  127: lost data  

    6.  330: lost data  

    7.  read over 

     

    关于HashMap的内部实现及jdk1.8改进了哪些,大有可讲,不在本文缀述。总之这里需要做对应的控制。方法有两点,要么用synchronized,要么用ConcurrentHashMap。改进代码如下:

    1.  private static final Object LOCK_OBJ = new Object();  

    2.  private static final Map<String, ThreadLocal<SimpleDateFormat>> SM = new HashMap<>();  

    3.    

    4.  private static SimpleDateFormat getSdf1(String pattern){  

    5.      ThreadLocal<SimpleDateFormat> tl = SM.get(pattern);  

    6.      if (tl == null){  

    7.          synchronized (LOCK_OBJ){  

    8.              tl = SM.computeIfAbsent(pattern, p -> ThreadLocal.withInitial(() -> new SimpleDateFormat(p)));  

    9.          }  

    10.     }  

    11.     return tl.get();  

    12. }  

    或者:

    1.  private static final Map<String, ThreadLocal<SimpleDateFormat>> SM = new ConcurrentHashMap<>();  

    2.    

    3.  private static SimpleDateFormat getSdf1(String pattern){  

    4.      ThreadLocal<SimpleDateFormat> tl = SM.get(pattern);  

    5.      if (tl == null){  

    6.          tl = ThreadLocal.withInitial(() -> new SimpleDateFormat(pattern));  

    7.          SM.putIfAbsent(pattern, tl);  

    8.      }  

    9.      return tl.get();  

    10.

    下面就要说这个ThreadLocal的坑爹地方了。

    当我查看他的性能损耗时,意外的发现和普通的new SimpleDateFormat()相差不大。Excuse me?

    后来我在代码里加这个行:

        

    1.  private static SimpleDateFormat getSdf1(String pattern){  

    2.      ThreadLocal<SimpleDateFormat> tl = SM.get(pattern);  

    3.      if (tl == null){  

    4.          System.out.println("tl == null");  

    5.          tl = ThreadLocal.withInitial(() -> {  

    6.              System.out.println("new pattern: " + pattern);  

    7.              return new SimpleDateFormat(pattern);  

    8.          });  

    9.          SM.putIfAbsent(pattern, tl);  

    10.     }  

    11.     return tl.get();  

    }  

    输出结果里发现居然每次使用的时候都会去初始化一遍SimpleDateFormat。虽然是解决了效率问题,但是还不如一个方案一来得方便。所以我放弃了这种写法。

     

    方案四:使用Joda-Time

    大名鼎鼎,不多介绍,直接上代码:

     

    1.  private static final Map<String, DateTimeFormatter> DM = new ConcurrentHashMap<>();  

    2.    

    3.  private static DateTimeFormatter getDf1(String pattern){  

    4.      DateTimeFormatter df = DM.get(pattern);  

    5.      if (df == null){  

    6.          df = DateTimeFormat.forPattern(pattern);  

    7.          DM.putIfAbsent(pattern, df);  

    8.      }  

    9.      return df;  

    10. }  

    11.   

    12. public static String format(Date date, String pattern){  

    13.     return new DateTime(date).toString(pattern);  

    14. }  

    15.   

    16. public static Date parse(String dateString, String pattern) throws ParseException {  

    17.     return DateTime.parse(dateString, getDf1(pattern)).toDate();  

    18. }  

     

    方案五:JDK8自带时间API

     

     

    在Java1.0中,对时间的支持只能依赖java.util.Date类。此类无法表示日期,只能以毫秒精度表示时间。更糟糕的是它的易用性,比如起始时间从1900年开始,月份从0开始,toString()方法包含JVM默认时区CET,但本身却并不支持时区。

           Java1.1面世后,Date类中很多方法被废弃,出来新的java.util.Calendar类。情况仍然很糟,Calendar的月份仍然从0开始,与Date同时存在却不能共用,在某些场合需要互转等。如DateFormat只在Date里有,但是SimpleDateFormat却是线程不安全的。

           终于Java8自带了优秀的时间API:java.time。

     

    1. public class Java8TimeUtil1 {  
    2.   
    3.     private static final Map<String, DateTimeFormatter> M = new ConcurrentHashMap<>();  
    4.   
    5.     private static DateTimeFormatter getDtf(String pattern){  
    6.         DateTimeFormatter dtf = M.get(pattern);  
    7.         if (dtf == null){  
    8.             DateTimeFormatter newDtf = DateTimeFormatter.ofPattern(pattern);  
    9.             dtf = M.putIfAbsent(pattern, newDtf);  
    10.             if (dtf == null){  
    11.                 dtf = newDtf;  
    12.             }  
    13.         }  
    14.         return dtf;  
    15.     }  
    16.   
    17.     public static LocalDateTime parse(String timeString, String pattern){  
    18.         return LocalDateTime.parse(timeString, getDtf(pattern));  
    19.     }  
    20.   
    21.     public static String format(LocalDateTime localDateTime, String pattern){  
    22.         return localDateTime.format(getDtf(pattern));  
    23.     }  
    24.   
    25.     public static void main(String[] args) {  
    26.         String dateStr1 = "2017-04-29 23";  
    27.         String pattern1 = "yyyy-MM-dd HH";  
    28.         String dateStr2 = "2018-03-19 23:29:34";  
    29.         String pattern2 = "yyyy-MM-dd HH:mm:ss";  
    30.         final int thredNum = 999999;  
    31.   
    32.         Callable<String> task1 = () -> Java8TimeUtil1.format(Java8TimeUtil1.parse(dateStr1, pattern1), pattern1);  
    33.         Callable<String> task2 = () -> Java8TimeUtil1.format(Java8TimeUtil1.parse(dateStr2, pattern2), pattern2);  
    34.   
    35.         ExecutorService threadPool = Executors.newFixedThreadPool(10);  
    36.         for (int i = 0; i < thredNum; i++){  
    37.             Future<String> future1 =  threadPool.submit(task1);  
    38.             Future<String> future2 =  threadPool.submit(task2);  
    39.             try {  
    40.                 if (!dateStr1.equals(future1.get())){  
    41.                     System.out.println("1 error: " + future1.get());  
    42.                 }  
    43.                 if (!dateStr2.equals(future2.get())){  
    44.                     System.out.println("2 error: " + future2.get());  
    45.                 }  
    46.             } catch (InterruptedException | ExecutionException e) {  
    47.                 e.printStackTrace();  
    48.             }  
    49.         }  
    50.         System.out.println("over");  
    51.     }  

    52. }  

     

    这也是现在我比较喜欢的方式。从运行过程看也是并行处理:

    1. start: 2018-03-24T21:24:25.235  
    2. 2018-03-24T21:24:25.248  
    3. 2018-03-24T21:24:25.248  
    4. 2018-03-24T21:24:26.259  
    5. 2018-03-24T21:24:26.259  
    6. 2018-03-24T21:24:27.260  
    7. 2018-03-24T21:24:27.260  
    8. 2018-03-24T21:24:28.261  
    9. 2018-03-24T21:24:28.261  
    10. 2018-03-24T21:24:29.261  
    11. 2018-03-24T21:24:29.261  
    12. 2018-03-24T21:24:30.262  
    13. 2018-03-24T21:24:30.262  
    14. 2018-03-24T21:24:31.263  
    15. 2018-03-24T21:24:31.263  
    16. 2018-03-24T21:24:32.264  
    17. 2018-03-24T21:24:32.264  

    18. over: 2018-03-24T21:24:33.264 

     

     

    同时9999万并发对性能并不会有很大消耗:

     

    图10

    最后,在方案五里,getDtf()方法里关于putIfAbsent()的使用方法和前面都不一样。这里其实也是一个坑。请读者们自行学习研究吧。

     

    如有错误请指出~ 

    转载注明出处。

    展开全文
  • 是java中常用的时间格式化工具,除了转化时间格式,还可以解析字符串到时间类型,方便好用,但是陷阱就在其中。如果你遇到类似看到时间字符串完全正确,但就是不能正确格式化,或者明明时间类型没错,但就是无法转化...

    SimpleDateFormat 陷阱

    SimpleDateFormat是java中常用的时间格式化工具,除了转化时间格式,还可以解析字符串到时间类型,方便好用,但是陷阱就在其中。如果你遇到类似看到时间字符串完全正确,但就是不能正确格式化,或者明明时间类型没错,但就是无法转化为其他格式,亦或是格式转化错误随机出现,那想想,是不是陷入了多线程并发的陷阱。

    调用:

    private static SimpleDateFormat sdf = new SimpleDataFormat("yyyy-MM-dd HH:mm:ss");
    ...
    ...
    
    public static Date parseDateFromString(String str){
         try{
             return sdf.parse(str);
         }catch(Exception e){
             //TODO
         }
    }
    ...
    ...

    多线程情况下数据随机抛出异常:

    java.lang.NumberFormatException: multiple points
            at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1082)
            at java.lang.Double.parseDouble(Double.java:510)
            at java.text.DigitList.getDouble(DigitList.java:151)
            at java.text.DecimalFormat.parse(DecimalFormat.java:1302)
            at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1934)
            at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1311)
            at java.text.DateFormat.parse(DateFormat.java:335)

    这种问题在单机情况下调试难以发现问题,根本原因在于SimpleDateFormat本身不是线程安全的:

    SimpleDateFormat.java

     *** Date formats 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
     * externally.
     *
     * @see          <a href="http://java.sun.com/docs/books/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>
     * @see          java.util.Calendar
     * @see          java.util.TimeZone
     * @see          DateFormat
     * @see          DateFormatSymbols
     * @version      1.88, 05/28/08
     * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
     */
    public class SimpleDateFormat extends DateFormat {
    **
    ..
    protected Calendar calendar;
    ..
        // Called from Format after creating a FieldDelegate
            private StringBuffer format(Date date, StringBuffer toAppendTo,
                                        FieldDelegate delegate) {
                // Convert input date to time field list
                calendar.setTime(date);
        .....
        }
        public Date parse(String text, ParsePosition pos)
        {
            int start = pos.index;
            int oldStart = start;
        int textLength = text.length();
        calendar.clear(); // Clears all the time fields
        ...
        ...
        }
    }

    从上面代码片段可以看到,多线程下,对象calendar会被频繁的修改,从而导致不可预知的错误。

    解决方案:

    1. SimpleDateFormat 实例化在方法内部,每次重新定义变量
      不好,导致format对象频繁创建,大并发下影响性能

    2. 同步代码块,对sdf共享变量加锁 synchronize(sdf)
      不好,并发下加锁会降低系统的并发性能,极限情况下退化为单线程,不推荐

    3. 使用ThreadLocal,隔离线程共享变量
      推荐使用,类似如下:

        private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<DateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
              return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
          }
      };
      public static Date parse(String dateStr) throws ParseException {
          return threadLocal.get().parse(dateStr);
      }public static String format(Date date) {
          return threadLocal.get().format(date);
      }

    结束

    展开全文
  • 需求:让多线程同时去解析日期 错误示范 public class Test1 { @Test public void test01() throws Exception { //格式化日期类 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); //任务类:用于...
  • 首先格式化可以有很种格式 SimpleDateformat("yyyy-MM-dd HH:mm:ss"); SimpleDateformat("yyyyMMddHHmmss");//年月日小时分钟秒 1、SimpleDateFormat是非线程安全的类,所以不能直接...

    首先格式化可以有很多种格式

    SimpleDateformat("yyyy-MM-dd HH:mm:ss");
    SimpleDateformat("yyyyMMddHHmmss");//年月日小时分钟秒

    1、SimpleDateFormat是非线程安全的类,所以不能直接修饰为static。这会导致发生线程安全问题。

    这个类在执行的时候是通过一个calendar变量来保存时间的,假如你定义为static的时候,那么calendar也将是共享的变量,那么多线程访问的时候,比如对时间进行增加,线程1加到为12月5号,这个时候线程1还没有执行完就被线程2执行了,线程2是加到为12月6号,线程2执行完后才轮到线程1继续执行,这个时候就会发现你本意是通过线程加到12月6号的,结果发现执行完确实5号。就是产生了线程安全问题。

    那么怎么解决呢?

    第一:就是不把Simpleformat作为全局变量,弄成局部变量

    第二:加线程安全锁

    第三:使用ThreadLocal,ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,也就不会出现那种线程抢占导致的问题了

    /**
    * 使用ThreadLocal定义一个全局的SimpleDateFormat
    */
    private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
       @Override
       protected SimpleDateFormat initialValue() {
           return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
       }
    };
    
    //用法
    String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());

     

    展开全文
  • 由于 DateFormat 是非线程安全的,因此在多线程并发情况下日期格式化时需要特别注意。下面记录几种格式化的方式: 线程不安全的处理方式 //线程不安全 private static final DateFormat DATE_FORMAT = new ...
  • SimpleDateFormat是是 Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但是DateFormat 和 SimpleDateFormat 类不都是线程安全的,在生产环境的多线程或高并发情况使用 format() 和 parse() ...
  • 目录传统日期时间格式化多线程安全问题写在前面为什么要有新的时间API传统日期时间格式的线程不安全问题Java8中全新的日期时间API 传统日期时间格式化多线程安全问题 写在前面 我们回顾一下,在我们Java8之前,...
  • // 2020-12-03 20:20:37 特点 线程不安全:如果我们把SimpleDateFormat定义成全局变量,个thread之间会共享这个SimpleDateFormat对象, 共同操作内部变量Calendar。 详解其线程不安全原因 解决办法 ...
  • Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 ...
  • // TODO 使用java8的线程安全时间格式化类 LocalDate localDate = LocalDate.now(); int month = localDate.getMonthValue(); String currentDate = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")...
  • SimpleDateFormat 是 JDK 提供的一个日期格式化和解析类,但它是非线程安全的,原因如下。 1. parse方法 Date parsedDate; try { parsedDate = calb.establish(calendar).getTime(); // If the year v...
  • 两篇文章 Java多线程中static变量的使用  (转自:... &amp;&amp;...SimpleDateFormat时间格式化存在线程安全问题 (https://www.cnblogs.com/zhuimengdeyuanyuan/archive/2017/10/25/7...
  • 那么在多线程中,就会出现错误的结果如下代码: public static void main(String[] args){ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd"); Callable&lt;Date&gt; callable = new...
  • 传统时间格式化线程安全问题 在使用SimpleDateFormat时间格式处理类会出现线程安全问题,而java8新特性中的 DateTimeFormatter时间格式处理类解决了此问题,代码如下: @Test public static void main(String[] ...
  • java线程安全的时间格式化

    千次阅读 2018-03-07 15:47:01
    这种办法使用简单,但是不灵活,如果有多种格式化的要求,则需要建很对象,而且当线程没有合理控制,不断死亡和增加,大量的threadLocal会增加gc负担 如果不想使用第三方库比如joda,基于java9实现: ...
  • 【从菜鸟到高手】日期格式化

    千次阅读 2018-10-15 18:30:55
    1 软件中的日期格式化 日期格式化就是对日期字符串进行解析和格式化输出。 在软件系统中,日期的形式与其被使用的阶段密切相关,展现层的日期数据为字符串,逻辑层的日期数据为 Date 对象,存储层的日期数据为...
  • java老鸟教你如何高效优雅的进行时间格式化
  • Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 ...
  • Java数字日期时间格式化处理

    千次阅读 2016-02-04 14:34:33
    日期时间格式化String.format()、SimpleDateFormat, 1、十进制数字格式化 使用DecimalFormat类(十进制数格式化)格式化数字,继承关系如下: java.lang.Object |-- java.text.Format |-- java.text....
  • xk-time是时间转换,时间计算,时间格式化,时间解析,日历,时间cron表达和时间NLP等的工具,使用Java8,线程安全,简单易用,个70个常用日期格式化模板,支持Java8时间类和日期,轻量级,无第三方依赖。...
  • Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 ...
  • Java 8 日期时间格式化

    千次阅读 2019-05-13 18:32:50
    6 时间格式化 7 相关文章 1 Instant Instant 表示基于世界标准时间(UTC)的时间线上的某一个时间点,通常用作事件的时间戳。 Instant 取值为 基于世界标准时间 从1970/01/01 00:00:00.000 到 某一...
  • 关于SimpleDateFormat安全的时间格式化线程安全问题

    万次阅读 多人点赞 2014-02-18 16:19:40
    Java 中一个非常常用的类,该类用来对日期字符串进行解析和格式化输出,但如果使用不小心会导致非常微妙和难以调试的问题,因为 DateFormat 和 SimpleDateFormat 类不都是线程安全的,在多线程环境下调用 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 266,757
精华内容 106,702
关键字:

日期格式化多线程