“ Java DateFormat ไม่ใช่ threadsafe” สิ่งนี้นำไปสู่อะไร


143

ทุกคนควรระวังเกี่ยวกับ Java DateFormat ที่ไม่ได้เป็นเธรดที่ปลอดภัยและฉันเข้าใจแนวคิดในทางทฤษฎี

แต่ฉันไม่สามารถมองเห็นปัญหาที่แท้จริงที่เราสามารถเผชิญได้เนื่องจากปัญหานี้ บอกว่าฉันเป็นเขตข้อมูล DateFormat ในชั้นเรียนและมีการใช้วิธีเดียวกันในชั้นเรียน (การจัดรูปแบบวันที่) ในสภาพแวดล้อมแบบมัลติเธรด

สาเหตุนี้จะ:

  • ข้อยกเว้นใด ๆ เช่นข้อยกเว้นรูปแบบ
  • ความคลาดเคลื่อนของข้อมูล
  • ปัญหาอื่น ๆ ?

นอกจากนี้โปรดอธิบายว่าทำไม


1
นี่คือสิ่งที่จะนำไปสู่: stackoverflow.com/questions/14309607/…
caw

ตอนนี้คือปี 2020 การรันการทดสอบของฉัน (ในแบบคู่ขนาน) พบว่าวันที่จากเธรดหนึ่งถูกส่งคืนแบบไม่ต่อเนื่องเมื่อเธรดอื่นพยายามจัดรูปแบบวันที่ เอาฉันสองสามสัปดาห์เพื่อตรวจสอบสิ่งที่มันขึ้นอยู่กับจนกว่าจะพบในการจัดรูปแบบที่ผู้สร้างสร้างอินสแตนซ์ของปฏิทินและปฏิทินมีการกำหนดค่าในภายหลังเพื่อใช้วันที่เราจัดรูปแบบ ในหัวของพวกเขายังคง 1990 ใครจะรู้.
Vlad Patryshev

คำตอบ:


264

ลองดูสิ

นี่คือโปรแกรมที่ใช้หลายเธรดร่วมกัน 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;
  }
}

นี่คือโพสต์ที่ดีพร้อมรายละเอียดเพิ่มเติม


1
ฉันชอบคำตอบนี้ :-)
Sundararaj Govindasamy

ฉันคิดว่าสาเหตุที่ทำให้นักพัฒนาหงุดหงิดอย่างนี้ก็คือในแวบแรกมันดูเหมือนว่ามันควรจะเป็นการเรียกฟังก์ชั่น เช่นสำหรับอินพุตเดียวกันฉันคาดหวังเอาต์พุตเดียวกัน (แม้ว่าจะมีหลายเธรดเรียกว่า) คำตอบที่ฉันเชื่อว่ามาจากนักพัฒนาของ Java ที่ไม่ได้ชื่นชม FOP ในขณะที่พวกเขาเขียนตรรกะวันเวลาดั้งเดิม ดังนั้นในที่สุดเราก็แค่พูดว่า "ไม่มีเหตุผลว่าทำไมมันถึงเป็นแบบนี้ไม่ใช่ความผิด"
Lezorte

30

ฉันคาดหวังว่าข้อมูลจะเสียหาย - เช่นถ้าคุณแยกวิเคราะห์วันที่สองวันพร้อมกันคุณอาจมีการโทรหนึ่งครั้งโดยข้อมูลจากอีกสายหนึ่ง

เป็นเรื่องง่ายที่จะจินตนาการว่าสิ่งนี้จะเกิดขึ้นได้อย่างไร: การแยกวิเคราะห์มักจะเกี่ยวข้องกับการคงสภาพจำนวนหนึ่งไว้กับสิ่งที่คุณอ่านมา หากทั้งสองเธรดมีการเหยียบย่ำในสถานะเดียวกันคุณจะพบปัญหา ยกตัวอย่างเช่นDateFormatตีแผ่calendarข้อมูลของประเภทCalendarและกำลังมองหารหัสของSimpleDateFormatวิธีการบางคนเรียกcalendar.set(...)และอื่น ๆ calendar.get(...)โทร ชัดเจนว่าไม่ปลอดภัยสำหรับเธรด

ฉันไม่ได้มองเข้าไปในที่แน่นอนรายละเอียดว่าทำไมDateFormatไม่ด้ายปลอดภัย แต่สำหรับฉันมันพอที่จะรู้ว่ามันเป็นที่ไม่ปลอดภัยโดยไม่ต้องประสาน - มารยาทที่แน่นอนของที่ไม่ใช่ความปลอดภัยแม้อาจมีการเปลี่ยนแปลงระหว่างรุ่น

โดยส่วนตัวแล้วฉันจะใช้ parsers จากJoda Timeแทนเนื่องจากเป็น thread ที่ปลอดภัย - และ Joda Time นั้นเป็น API วันที่และเวลาที่ดีกว่ามากในการเริ่มต้นด้วย :)


1
+1 jodatime และ sonar เพื่อบังคับใช้: mestachs.wordpress.com/2012/03/17/…
mestachs

18

หากคุณกำลังใช้ Java 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

10

ประมาณว่าคุณไม่ควรกำหนดเป็นตัวอย่างตัวแปรของวัตถุเข้าถึงได้โดยหัวข้อจำนวนมากหรือDateFormatstatic

รูปแบบวันที่จะไม่ซิงโครไนซ์ ขอแนะนำให้สร้างอินสแตนซ์รูปแบบแยกต่างหากสำหรับแต่ละเธรด

ดังนั้นในกรณีที่คุณ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());  
    }
}

นอกจากนี้ในทุกกรณีไม่มี static DateFormat

ตามที่ระบุไว้โดย Jon Skeet คุณสามารถมีตัวแปรอินสแตนซ์ทั้งแบบคงที่และแบบแบ่งใช้ในกรณีที่คุณทำการซิงโครไนซ์ภายนอก (เช่นใช้synchronizedรอบการโทรไปที่DateFormat)


2
ฉันไม่เห็นสิ่งที่ตามมาเลย ฉันไม่ได้ใช้ประโยชน์จาก thread-safe ของฉันอย่างเต็มที่ดังนั้นฉันไม่คาดหวังว่าตัวแปรอินสแตนซ์ของพวกเขาจะเป็น thread-safe เช่นกัน มีเหตุผลมากกว่าที่จะบอกว่าคุณไม่ควรจัดเก็บ DateFormat ในตัวแปรแบบคงที่หรือถ้าคุณทำคุณจะต้องทำการซิงโครไนซ์
Jon Skeet

1
โดยทั่วไปจะดีกว่า - แม้ว่าจะเป็นไปได้ที่จะมี DateFormat คงที่หากคุณทำข้อมูลให้ตรงกัน ที่อาจทำงานได้ดีขึ้นในหลายกรณีมากกว่าการสร้างใหม่SimpleDateFormatบ่อยมาก มันจะขึ้นอยู่กับรูปแบบการใช้งาน
Jon Skeet

1
คุณช่วยอธิบายได้อย่างไรว่าอินสแตนซ์แบบสแตติกสามารถทำให้เกิดปัญหาในสภาพแวดล้อมแบบมัลติเธรดได้อย่างไร
Alexandr

4
เพราะร้านค้าคำนวณสื่อกลางในการตัวแปรเช่นและที่ไม่ด้ายปลอดภัย
Bozho

2

รูปแบบวันที่จะไม่ซิงโครไนซ์ ขอแนะนำให้สร้างอินสแตนซ์รูปแบบแยกต่างหากสำหรับแต่ละเธรด หากหลายเธรดเข้าถึงรูปแบบพร้อมกันจะต้องซิงโครไนซ์ภายนอก

ซึ่งหมายความว่าคุณมีวัตถุของ DateFormat และคุณกำลังเข้าถึงวัตถุเดียวกันจากสองเธรดที่แตกต่างกันและคุณกำลังเรียกใช้วิธีการจัดรูปแบบตามวัตถุนั้นทั้งสองเธรดจะเข้าสู่วิธีการเดียวกันในเวลาเดียวกันบนวัตถุเดียวกันเพื่อให้คุณเห็นภาพ ไม่ได้ผลลัพธ์ที่เหมาะสม

หากคุณต้องทำงานกับ DateFormat คุณควรทำอะไร

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

1

ข้อมูลเสียหาย เมื่อวานฉันสังเกตเห็นมันในโปรแกรมมัลติเธรดของฉันที่ฉันมีDateFormatวัตถุคงที่และเรียกมันformat()เป็นค่าที่อ่านผ่าน JDBC ฉันมีคำสั่งเลือก SQL ที่ฉันอ่านวันเดียวกันกับชื่ออื่น ( SELECT date_from, date_from AS date_from1 ...) งบดังกล่าวถูกใช้ใน 5 หัวข้อสำหรับวันที่ต่างๆในWHEREclasue วันที่ดู "ปกติ" แต่ค่าแตกต่างกัน - ในขณะที่วันที่ทั้งหมดมาจากปีเดียวกันเท่านั้นเดือนและวันที่เปลี่ยน

คำตอบอื่น ๆ แสดงวิธีการหลีกเลี่ยงความเสียหายดังกล่าว ฉันทำให้ฉันDateFormatไม่คงที่ตอนนี้มันเป็นสมาชิกของคลาสที่เรียกคำสั่ง SQL ฉันทดสอบสแตติกเวอร์ชันด้วยการซิงโครไนซ์ ทั้งสองทำงานได้ดีโดยไม่มีความแตกต่างด้านประสิทธิภาพ


1

ข้อมูลจำเพาะของรูปแบบ, NumberFormat, DateFormat, MessageFormat, ฯลฯ ไม่ได้ถูกออกแบบมาให้ปลอดภัยต่อเธรด นอกจากนี้เมธอด parse เรียกCalendar.clone()ใช้เมธอดและมีผลกับรอยเท้าปฏิทินดังนั้นการแยกเธรดจำนวนมากพร้อมกันจะเปลี่ยนการโคลนของอินสแตนซ์ปฏิทิน

สำหรับข้อมูลเพิ่มเติมนี่คือรายงานข้อผิดพลาดเช่นนี้และสิ่งนี้พร้อมผลลัพธ์ของปัญหาความปลอดภัยของเธรด DateFormat


1

ในคำตอบที่ดีที่สุด dogbane ให้ตัวอย่างของการใช้parseฟังก์ชั่นและสิ่งที่มันนำไปสู่ ด้านล่างเป็นรหัสที่ให้คุณตรวจสอบการformatทำงาน

โปรดสังเกตว่าถ้าคุณเปลี่ยนจำนวนตัวจัดการ (เธรดที่เกิดขึ้นพร้อมกัน) คุณจะได้รับผลลัพธ์ที่แตกต่างกัน จากการทดลองของฉัน:

  • ปล่อยให้newFixedThreadPoolตั้งค่าเป็น 5 และการวนซ้ำจะล้มเหลวทุกครั้ง
  • ตั้งค่าเป็น 1 และการวนซ้ำจะทำงานเสมอ (เห็นได้ชัดว่างานทั้งหมดจะทำงานทีละตัว)
  • การตั้งค่าเป็น 2 และการวนซ้ำมีเพียงประมาณ 6% โอกาสในการทำงาน

ฉันคาดเดา YMMV ขึ้นอยู่กับโปรเซสเซอร์ของคุณ

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");
}

0

หากมีหลายเธรดที่จัดการ / เข้าถึงอินสแตนซ์ DateFormat เดียวและไม่ได้ใช้การซิงโครไนซ์ก็เป็นไปได้ที่จะได้รับผลลัพธ์ที่มีสัญญาณรบกวน นั่นเป็นเพราะการดำเนินการที่ไม่ใช่อะตอมหลายครั้งอาจมีการเปลี่ยนสถานะ


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

OP ถามว่าทำไมสิ่งนี้เกิดขึ้นเช่นเดียวกับอะไร
Adam

0

สิ่งนี้จะทำให้ ArrayIndexOutOfBoundsException

นอกเหนือจากผลลัพธ์ที่ไม่ถูกต้องมันจะทำให้คุณผิดพลาดเป็นครั้งคราว ขึ้นอยู่กับความเร็วของเครื่อง ในแล็ปท็อปของฉันมันเกิดขึ้นหนึ่งครั้งโดยเฉลี่ย 100,000 ครั้ง:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<?> future1 = executorService.submit(() -> {
  for (int i = 0; i < 99000; i++) {
    sdf.format(Date.from(LocalDate.parse("2019-12-31").atStartOfDay().toInstant(UTC)));
  }
});

executorService.submit(() -> {
  for (int i = 0; i < 99000; i++) {
    sdf.format(Date.from(LocalDate.parse("2020-04-17").atStartOfDay().toInstant(UTC)));
  }
});

future1.get();

บรรทัดสุดท้ายจะทริกเกอร์ข้อยกเว้นของ executor ที่เลื่อนออกไป:

java.lang.ArrayIndexOutOfBoundsException: Index 16 out of bounds for length 13
  at java.base/sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:453)
  at java.base/java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2394)
  at java.base/java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2309)
  at java.base/java.util.Calendar.complete(Calendar.java:2301)
  at java.base/java.util.Calendar.get(Calendar.java:1856)
  at java.base/java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:1150)
  at java.base/java.text.SimpleDateFormat.format(SimpleDateFormat.java:997)
  at java.base/java.text.SimpleDateFormat.format(SimpleDateFormat.java:967)
  at java.base/java.text.DateFormat.format(DateFormat.java:374)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.