2010-10-26 3 views
107

जावा डेटफॉर्मैट के बारे में सभी सावधानियां थ्रेड सुरक्षित नहीं हैं और मैं सैद्धांतिक रूप से अवधारणा को समझता हूं।"जावा डेटफॉर्मैट थ्रेडसेफ नहीं है" इससे क्या होता है?

लेकिन मैं यह देखने में सक्षम नहीं हूं कि इसके कारण हम कौन से वास्तविक मुद्दों का सामना कर सकते हैं। कहें, मेरे पास एक कक्षा में डेटफॉर्मेट फ़ील्ड है और इसका उपयोग बहु-थ्रेडेड वातावरण में कक्षा (स्वरूपण तिथियों) में विभिन्न विधियों में किया जाता है।

क्या यह कारण:

  • प्रारूप अपवाद
  • डेटा
  • किसी अन्य अंक में विसंगति की तरह किसी भी अपवाद?

इसके अलावा, कृपया बताएं क्यों।

+1

यह वह जगह है यह सुराग क्या करने के लिए: http://stackoverflow.com/questions/14309607/unexpected-side-effects-when-parsing-dates-in-android – caw

उत्तर

220

आइए इसे आज़माएं।

यहां एक प्रोग्राम है जिसमें एकाधिक धागे साझा SimpleDateFormat साझा करते हैं।

कार्यक्रम:

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

    final DateFormat format = new SimpleDateFormat("yyyyMMdd"); 

    Callable<Date> task = new Callable<Date>(){ 
     public Date call() throws Exception { 
      return format.parse("20101022"); 
     } 
    }; 

    //pool with 5 threads 
    ExecutorService exec = Executors.newFixedThreadPool(5); 
    List<Future<Date>> results = new ArrayList<Future<Date>>(); 

    //perform 10 date conversions 
    for(int i = 0 ; i < 10 ; i++){ 
     results.add(exec.submit(task)); 
    } 
    exec.shutdown(); 

    //look at the results 
    for(Future<Date> result : results){ 
     System.out.println(result.get()); 
    } 
} 

भागो इस कई बार और आप देखेंगे:

अपवाद:

1.

:

यहाँ कुछ उदाहरण हैं

Caused by: java.lang.NumberFormatException: For input string: "" 
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48) 
    at java.lang.Long.parseLong(Long.java:431) 
    at java.lang.Long.parseLong(Long.java:468) 
    at java.text.DigitList.getLong(DigitList.java:177) 
    at java.text.DecimalFormat.parse(DecimalFormat.java:1298) 
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) 

2।

Caused by: java.lang.NumberFormatException: For input string: ".10201E.102014E4" 
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224) 
    at java.lang.Double.parseDouble(Double.java:510) 
    at java.text.DigitList.getDouble(DigitList.java:151) 
    at java.text.DecimalFormat.parse(DecimalFormat.java:1303) 
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1589) 

3.

Caused by: java.lang.NumberFormatException: multiple points 
    at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1084) 
    at java.lang.Double.parseDouble(Double.java:510) 
    at java.text.DigitList.getDouble(DigitList.java:151) 
    at java.text.DecimalFormat.parse(DecimalFormat.java:1303) 
    at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1936) 
    at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1312) 

गलत परिणाम:

Sat Oct 22 00:00:00 BST 2011 
Thu Jan 22 00:00:00 GMT 1970 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Thu Oct 22 00:00:00 GMT 1970 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 

सही परिणाम:

Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 
Fri Oct 22 00:00:00 BST 2010 

एक और दृष्टिकोण को सुरक्षित रूप से एक बहु थ्रेडेड वातावरण में DateFormats उपयोग करने के लिए एक ThreadLocal चर का उपयोग करने के लिए DateFormat वस्तु धारण करने के लिए, इसका मतलब है कि प्रत्येक थ्रेड अपनी एक प्रतिलिपि होगा और अन्य धागे को रिहा करने के लिए प्रतीक्षा करने की जरूरत नहीं है जो है यह।

public class DateFormatTest { 

    private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){ 
    @Override 
    protected DateFormat initialValue() { 
     return new SimpleDateFormat("yyyyMMdd"); 
    } 
    }; 

    public Date convert(String source) throws ParseException{ 
    Date d = df.get().parse(source); 
    return d; 
    } 
} 

यहाँ अधिक विवरण के साथ एक अच्छा post है: यह कैसे है।

+12

मुझे उन उत्तरों से प्यार है जिनमें एक रननेबल उदाहरण शामिल है। मैंने एक वर्ग में 'मुख्य' चिपकाया, इसे चलाया, और आपके कुछ परिणामों को दोबारा बनाया। धन्यवाद! – DavidS

+0

महान उदाहरण लेकिन ओपी ने यह भी पूछा कि क्यों। – Adam

9

काफी हद तक, आपको DateFormat को कई धागे, या static द्वारा एक्सेस किए गए ऑब्जेक्ट के आवृत्ति चर के रूप में परिभाषित नहीं करना चाहिए।

दिनांक प्रारूप सिंक्रनाइज़ नहीं हैं। प्रत्येक थ्रेड के लिए अलग प्रारूप उदाहरण बनाने की अनुशंसा की जाती है।

तो, मामले में अपने Foo.handleBar(..) से अधिक थ्रेड से, बजाय पहुँचा जा सकता है:

public class Foo { 
    private DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); 

    public void handleBar(Bar bar) { 
     bar.setFormattedDate(df.format(bar.getStringDate()); 
    } 
} 

आप उपयोग करना चाहिए:

public class Foo { 

    public void handleBar(Bar bar) { 
     DateFormat df = new SimpleDateFormat("dd/mm/yyyy"); 
     bar.setFormattedDate(df.format(bar.getStringDate()); 
    } 
} 

इसके अलावा, सभी मामलों में, नहीं है एक staticDateFormat

जैसा कि जॉन स्कीट द्वारा उल्लेख किया गया है, आप दोनों स्थिर और साझा उदाहरण भिन्न हो सकते हैं यदि आप बाह्य सिंक्रनाइज़ेशन करते हैं तो ईएस (यानी। synchronizedDateFormat पर कॉल के आसपास)

+1

मैं नहीं दिख रहा है वह सब कुछ भी है। मैं अपने अधिकांश प्रकार थ्रेड-सुरक्षित नहीं बनाता, इसलिए मुझे उम्मीद नहीं है कि उनके आवृत्ति चर या तो थ्रेड-सुरक्षित हो, जरूरी है। यह कहना अधिक उचित है कि आपको एक * स्थिर * चर में डेटफॉर्मेट स्टोर नहीं करना चाहिए - या यदि आप करते हैं, तो आपको सिंक्रनाइज़ेशन की आवश्यकता होगी। –

+0

@ जोन स्कीट मैंने स्पष्ट किया। – Bozho

+0

यह आमतौर पर बेहतर होता है - हालांकि यदि आपने * सिंक्रनाइज़ किया है तो स्थिर दिनांकफॉर्मेट होना ठीक होगा। यह बहुत से नए 'SimpleDateFormat' को अक्सर बनाने के बजाय कई मामलों में बेहतर प्रदर्शन कर सकता है। यह उपयोग पैटर्न पर निर्भर करेगा। –

2

दिनांक प्रारूप सिंक्रनाइज़ नहीं किए गए हैं। प्रत्येक थ्रेड के लिए अलग प्रारूप उदाहरण बनाने की अनुशंसा की जाती है। यदि एकाधिक थ्रेड एक प्रारूप को एक साथ एक्सेस करते हैं, तो इसे को बाहरी रूप से सिंक्रनाइज़ किया जाना चाहिए।

इसका मतलब यह है मान लीजिए आप DateFormat की एक वस्तु है और आप दो अलग अलग धागे से एक ही वस्तु तक पहुँच रहे हैं और आपको लगता है कि वस्तु दोनों धागा एक ही वस्तु पर एक ही समय में एक ही विधि पर प्रवेश करेंगे पर प्रारूप विधि बुला रहे हैं ताकि आप आप DateFormat के साथ काम करना है, तो कल्पना कर सकते हैं यह उचित परिणाम

का कारण नहीं बनेगा किसी भी कैसे तो आप कुछ करना चाहिए

public synchronized myFormat(){ 
// call here actual format method 
} 
+0

नहीं है इससे हल होने से अधिक समस्याएं हो सकती हैं। जोडाटाइम की सिफारिश के लिए – Chad

0

यदि एकाधिक थ्रेड्स एक एकल डेटफॉर्मेट उदाहरण और सिंक्रनाइज़ेशन का उपयोग नहीं करते हैं, तो इसका उपयोग नहीं किया जाता है, तो स्कैम्बल किए गए परिणाम प्राप्त करना संभव है। ऐसा इसलिए है क्योंकि कई गैर-परमाणु संचालन राज्य बदल रहे हैं या स्मृति को असंगत रूप से देख सकते हैं।

28

मुझे डेटा भ्रष्टाचार की उम्मीद होगी - उदा। यदि आप एक ही समय में दो तिथियों को पार्स कर रहे हैं, तो आप किसी अन्य डेटा से प्रदूषित एक कॉल कर सकते हैं।

कल्पना करना आसान है कि यह कैसे हो सकता है: पार्सिंग में अक्सर एक निश्चित मात्रा में राज्य को बनाए रखना शामिल होता है जो आपने अभी तक पढ़ा है। यदि दो धागे दोनों एक ही राज्य पर ट्रामलिंग कर रहे हैं, तो आपको समस्याएं मिलेंगी। उदाहरण के लिए, DateFormatcalendar प्रकार Calendar के क्षेत्र का खुलासा करता है, और SimpleDateFormat के कोड को देखता है, कुछ विधियां calendar.set(...) पर कॉल करती हैं और अन्य calendar.get(...) पर कॉल करती हैं। यह स्पष्ट रूप से थ्रेड-सुरक्षित नहीं है।

मैं में क्यों DateFormat थ्रेड-सुरक्षित नहीं है सटीक विवरण देखा नहीं है, लेकिन मेरे लिए यह पता चला है कि यह तुल्यकालन के बिना असुरक्षित है काफी है - गैर सुरक्षा का सही शिष्टाचार भी कर सकते थे रिलीज के बीच बदलें।

व्यक्तिगत तौर पर मैं बजाय Joda Time से पारसर्स का प्रयोग करेंगे, जैसा कि वे धागा सुरक्षित हैं - और Joda समय :)

+3

+1। –

+1

+1 जोडाटाइम और सोनार इसके उपयोग को लागू करने के लिए: http://mestachs.wordpress.com/2012/03/17/dangerous-can-be-dating-in-java-joda-to-the-rescue/ – mestachs

+0

+1 आपको यह स्वीकार करने के लिए नहीं पता कि क्यों 'डेटफॉर्मैट' थ्रेडसेफ नहीं है, भले ही आपको समझाने के लिए परेशान न हो। – Adam

1

डाटा दूषित है साथ शुरू करने के लिए एक बेहतर दिनांक और समय एपीआई है। कल मैंने इसे अपने मल्टीथ्रेड प्रोग्राम में देखा जहां मेरे पास स्थिर DateFormat ऑब्जेक्ट था और जेडीबीसी के माध्यम से पढ़ने वाले मानों के लिए format() कहा जाता था। मेरे पास एसक्यूएल का चयन कथन था जहां मैंने अलग-अलग नामों के साथ उसी तारीख को पढ़ा (SELECT date_from, date_from AS date_from1 ...)। WHERE क्लैस्यू में विभिन्न तिथियों के लिए इस तरह के वक्तव्य 5 धागे में उपयोग कर रहे थे। तिथियों ने "सामान्य" देखा लेकिन वे मूल्य में भिन्न थे - जबकि सभी तिथियां उसी वर्ष से केवल एक महीने और दिन बदल गईं।

अन्य उत्तर आपको ऐसे भ्रष्टाचार से बचने का तरीका दिखाते हैं। मैंने अपना DateFormat स्थिर नहीं बनाया है, अब यह एक वर्ग का सदस्य है जो SQL कथन कहता है। मैंने सिंक्रनाइज़िंग के साथ भी स्थिर संस्करण का परीक्षण किया। दोनों प्रदर्शन में कोई फर्क नहीं पड़ता के साथ अच्छी तरह से काम किया।

1

प्रारूप, संख्याफॉर्मेट, डेटफॉर्मेट, संदेशफॉर्मैट इत्यादि के विनिर्देश थ्रेड-सुरक्षित होने के लिए डिज़ाइन नहीं किए गए थे। साथ ही, पार्स विधि Calendar.clone() विधि पर कॉल करती है और यह कैलेंडर पैरों के निशान को प्रभावित करती है, इसलिए समसामयिक रूप से पारदर्शी कई धागे कैलेंडर उदाहरण के क्लोनिंग को बदल देंगे।

अधिक जानकारी के लिए, this और this जैसी बग रिपोर्ट्स हैं, डेटफॉर्मेट थ्रेड-सुरक्षा समस्या के परिणाम के साथ।

5

यदि आप जावा 8 का उपयोग कर रहे हैं तो आप DateTimeFormatter का उपयोग कर सकते हैं।

पैटर्न से बनाए गए एक फॉर्मेटर को जितनी बार आवश्यक हो सकता है, यह अपरिवर्तनीय है और थ्रेड-सुरक्षित है।

कोड:

LocalDate date = LocalDate.now(); 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); 
String text = date.format(formatter); 
System.out.println(text); 

आउटपुट:

2017-04-17 
0

यह मेरा सरल कोड है कि पता चलता DateFormat सुरक्षित थ्रेड नहीं है।

import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.Locale; 

public class DateTimeChecker { 
    static DateFormat df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); 
    public static void main(String args[]){ 
     String target1 = "Thu Sep 28 20:29:30 JST 2000"; 
     String target2 = "Thu Sep 28 20:29:30 JST 2001"; 
     String target3 = "Thu Sep 28 20:29:30 JST 2002"; 
     runThread(target1); 
     runThread(target2); 
     runThread(target3); 
    } 
    public static void runThread(String target){ 
     Runnable myRunnable = new Runnable(){ 
      public void run(){ 

      Date result = null; 
      try { 
       result = df.parse(target); 
      } catch (ParseException e) { 
       e.printStackTrace(); 
       System.out.println("Ecxfrt"); 
      } 
      System.out.println(Thread.currentThread().getName() + " " + result); 
     } 
     }; 
     Thread thread = new Thread(myRunnable); 

     thread.start(); 
    } 
} 

चूंकि सभी थ्रेड एक ही SimpleDateFormat ऑब्जेक्ट का उपयोग कर रहे हैं, यह निम्न अपवाद फेंकता है।

Exception in thread "Thread-0" Exception in thread "Thread-2" Exception in thread "Thread-1" java.lang.NumberFormatException: multiple points 
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) 
at sun.misc.FloatingDecimal.parseDouble(Unknown Source) 
at java.lang.Double.parseDouble(Unknown Source) 
at java.text.DigitList.getDouble(Unknown Source) 
at java.text.DecimalFormat.parse(Unknown Source) 
at java.text.SimpleDateFormat.subParse(Unknown Source) 
at java.text.SimpleDateFormat.parse(Unknown Source) 
at java.text.DateFormat.parse(Unknown Source) 
at DateTimeChecker$1.run(DateTimeChecker.java:24) 
at java.lang.Thread.run(Unknown Source) 
java.lang.NumberFormatException: multiple points 
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) 
at sun.misc.FloatingDecimal.parseDouble(Unknown Source) 
at java.lang.Double.parseDouble(Unknown Source) 
at java.text.DigitList.getDouble(Unknown Source) 
at java.text.DecimalFormat.parse(Unknown Source) 
at java.text.SimpleDateFormat.subParse(Unknown Source) 
at java.text.SimpleDateFormat.parse(Unknown Source) 
at java.text.DateFormat.parse(Unknown Source) 
at DateTimeChecker$1.run(DateTimeChecker.java:24) 
at java.lang.Thread.run(Unknown Source) 
java.lang.NumberFormatException: multiple points 
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source) 
at sun.misc.FloatingDecimal.parseDouble(Unknown Source) 
at java.lang.Double.parseDouble(Unknown Source) 
at java.text.DigitList.getDouble(Unknown Source) 
at java.text.DecimalFormat.parse(Unknown Source) 
at java.text.SimpleDateFormat.subParse(Unknown Source) 
at java.text.SimpleDateFormat.parse(Unknown Source) 
at java.text.DateFormat.parse(Unknown Source) 
at DateTimeChecker$1.run(DateTimeChecker.java:24) 
at java.lang.Thread.run(Unknown Source) 

लेकिन अगर हम अलग धागे के लिए विभिन्न वस्तुओं गुजरती हैं, कोड त्रुटियों के बिना चलाता है।

import java.text.ParseException; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.Locale; 

public class DateTimeChecker { 
    static DateFormat df; 
    public static void main(String args[]){ 
     String target1 = "Thu Sep 28 20:29:30 JST 2000"; 
     String target2 = "Thu Sep 28 20:29:30 JST 2001"; 
     String target3 = "Thu Sep 28 20:29:30 JST 2002"; 
     df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); 
     runThread(target1, df); 
     df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); 
     runThread(target2, df); 
     df = new SimpleDateFormat("EEE MMM dd kk:mm:ss z yyyy", Locale.ENGLISH); 
     runThread(target3, df); 
    } 
    public static void runThread(String target, DateFormat df){ 
     Runnable myRunnable = new Runnable(){ 
     public void run(){ 

      Date result = null; 
      try { 
       result = df.parse(target); 
      } catch (ParseException e) { 
       e.printStackTrace(); 
       System.out.println("Ecxfrt"); 
      } 
      System.out.println(Thread.currentThread().getName() + " " + result); 
     } 
     }; 
     Thread thread = new Thread(myRunnable); 

     thread.start(); 
    } 
} 

ये परिणाम हैं।

Thread-0 Thu Sep 28 17:29:30 IST 2000 
Thread-2 Sat Sep 28 17:29:30 IST 2002 
Thread-1 Fri Sep 28 17:29:30 IST 2001 
+0

ओपी ने पूछा कि यह क्यों होता है और साथ ही क्या होता है। – Adam

0

सर्वश्रेष्ठ उत्तर dogbane में parse समारोह और क्या यह करने के लिए सुराग का उपयोग करने का उदाहरण दिया। नीचे एक कोड है जो आपको format फ़ंक्शन की जांच करने देता है।

ध्यान दें कि यदि आप निष्पादकों (समवर्ती धागे) की संख्या बदलते हैं तो आपको अलग-अलग परिणाम मिलेंगे। मेरे प्रयोगों से:

  • newFixedThreadPool छोड़ें 5 पर सेट करें और लूप हर बार विफल हो जाएगा।
  • 1 पर सेट करें और लूप हमेशा काम करेगा (जाहिर है क्योंकि सभी कार्य वास्तव में एक-एक करके चलते हैं)
  • 2 पर सेट करें और लूप में केवल 6% काम करने का मौका है।

मैं आपके प्रोसेसर के आधार पर वाईएमएमवी का अनुमान लगा रहा हूं।

format फ़ंक्शन एक अलग थ्रेड से समय स्वरूपण में विफल रहता है। ऐसा इसलिए है क्योंकि आंतरिक रूप से format फ़ंक्शन calendar ऑब्जेक्ट का उपयोग कर रहा है जो format फ़ंक्शन की शुरुआत में स्थापित है। और calendar ऑब्जेक्ट SimpleDateFormat कक्षा की एक संपत्ति है। आह ...

/** 
* Test SimpleDateFormat.format (non) thread-safety. 
* 
* @throws Exception 
*/ 
private static void testFormatterSafety() throws Exception { 
    final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
    final Calendar calendar1 = new GregorianCalendar(2013,1,28,13,24,56); 
    final Calendar calendar2 = new GregorianCalendar(2014,1,28,13,24,56); 
    String expected[] = {"2013-02-28 13:24:56", "2014-02-28 13:24:56"}; 

    Callable<String> task1 = new Callable<String>() { 
     @Override 
     public String call() throws Exception { 
      return "0#" + format.format(calendar1.getTime()); 
     } 
    }; 
    Callable<String> task2 = new Callable<String>() { 
     @Override 
     public String call() throws Exception { 
      return "1#" + format.format(calendar2.getTime()); 
     } 
    }; 

    //pool with X threads 
    // note that using more then CPU-threads will not give you a performance boost 
    ExecutorService exec = Executors.newFixedThreadPool(5); 
    List<Future<String>> results = new ArrayList<>(); 

    //perform some date conversions 
    for (int i = 0; i < 1000; i++) { 
     results.add(exec.submit(task1)); 
     results.add(exec.submit(task2)); 
    } 
    exec.shutdown(); 

    //look at the results 
    for (Future<String> result : results) { 
     String answer = result.get(); 
     String[] split = answer.split("#"); 
     Integer calendarNo = Integer.parseInt(split[0]); 
     String formatted = split[1]; 
     if (!expected[calendarNo].equals(formatted)) { 
      System.out.println("formatted: " + formatted); 
      System.out.println("expected: " + expected[calendarNo]); 
      System.out.println("answer: " + answer); 
      throw new Exception("formatted != expected"); 
     /** 
     } else { 
      System.out.println("OK answer: " + answer); 
     /**/ 
     } 
    } 
    System.out.println("OK: Loop finished"); 
}