2017-10-21 171 views
0

我的應用程序並行處理一堆文件,並且由於每個文件都有文件作爲其名稱的一部分被刪除的日期,因此我使用以下方法來提取刪除日期:Java - 靜態方法的線程安全

public static Optional<Date> getFileDropDate(String filename) { 
     /* Example: my_file-1_20171002.xml */ 
     if (StringUtils.isEmpty(filename) || !filename.matches(Constants.FILE_DATE_PATTERN)) { 
      return Optional.empty(); 
     } 
     String[] segments = filename.split(UNDERSCORE); 
     try { 
      // Filename definitely ends with _yyyyMMdd.xml as it matches the pattern. Split by underscore, get the last segment and take out the .xml 
      return Optional.of(yyyymmdd.parse(segments[segments.length - 1].replace(Constants.XML_EXTENSION, ""))); 
     } catch (ParseException e) { 
      logger.error("Unable to parse filename to find load date"); 
      return Optional.empty(); 
     } 
    } 

雖然功能是非常穩固和我的測試都是成功的,當我在多線程設置運行它,它失敗了日期解析例外,並且該方法試圖解析字符串是完全不可思議。

例如:

java.lang.NumberFormatException: For input string: "E.21616" 
    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:2162) 
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) 
    at java.text.DateFormat.parse(DateFormat.java:364) 
    at mycompany.importer.parser.ParserUtil.getFileDropDate(ParserUtil.java:308) 
    at mycompany.dataprocessors.FileProcessor.lambda$importData$2(FileProcessor.java:181) 
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) 
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) 
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) 
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291) 
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731) 
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) 
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) 
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) 
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) 
2017-10-21 22:11:48.550 [ForkJoinPool.commonPool-worker-2] ERROR a.c.t.m.m.a.m.i.d.FileProcessor - Error in parsing files and building datamap. Feed file is: /files/my_file_1-db_20171016.xml 
java.lang.NumberFormatException: For input string: "E.21616E2" 
    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:2162) 
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) 
    at java.text.DateFormat.parse(DateFormat.java:364) 
    at mycompany.importer.parser.ParserUtil.getFileDropDate(ParserUtil.java:308) 
    at mycompany.dataprocessors.FileProcessor.lambda$importData$2(FileProcessor.java:181) 
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) 
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) 
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) 
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291) 
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731) 
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) 
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) 
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) 
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) 
2017-10-21 22:11:48.550 [ForkJoinPool.commonPool-worker-3] ERROR a.c.t.m.m.a.m.i.d.FileProcessor - Error in parsing files and building datamap. Feed file is: /files/my_file_2-db_20171020.xml 
java.lang.NumberFormatException: For input string: "20172017E4" 
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 
    at java.lang.Long.parseLong(Long.java:589) 
    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:1867) 
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) 
    at java.text.DateFormat.parse(DateFormat.java:364) 
    at mycompany.importer.parser.ParserUtil.getFileDropDate(ParserUtil.java:308) 
    at mycompany.dataprocessors.FileProcessor.lambda$importData$2(FileProcessor.java:181) 
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) 
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374) 
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) 
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291) 
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731) 
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) 
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) 
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) 
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) 

製作方法進行同步解決了這個問題,但考慮到這種方法不使用或修改任何實例變量(如果這不是明顯在常量全部的變量是公共static final),每個線程在技術上都有自己的局部變量棧,爲什麼這個方法不是線程安全的?

+1

'yyyymmdd'有可能是'static'對象嗎?這就是原因,這個類不是爲多線程而設計的,* *有*實例值。我的€0.02:*避免靜態*。 –

回答

9

原因是這個yyyymmdd不是線程安全的。來自documentation

日期格式不同步。建議爲每個線程創建單獨的格式實例。如果多個線程同時訪問一個格式,它必須在外部同步。

不要在線程之間共享單個SimpleDateFormat,因爲它不是線程安全的。在該方法中創建一個新的SimpleDateFormat,或者使用ThreadLocal,或將所有訪問同步到yyyymmdd或更好地使用線程安全的java.time類。