การซิงโครไนซ์การเข้าถึง SimpleDateFormat


91

javadoc สำหรับ SimpleDateFormat ระบุว่า SimpleDateFormat ไม่ซิงโครไนซ์

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

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

ตัวเลือกที่ 1: สร้างอินสแตนซ์ในเครื่องเมื่อจำเป็น

public String formatDate(Date d) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(d);
}

ตัวเลือกที่ 2: สร้างอินสแตนซ์ของ SimpleDateFormat เป็นตัวแปรคลาส แต่ซิงโครไนซ์การเข้าถึง

private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public String formatDate(Date d) {
    synchronized(sdf) {
        return sdf.format(d);
    }
}

ตัวเลือกที่ 3: สร้าง ThreadLocal เพื่อเก็บอินสแตนซ์ของ SimpleDateFormat ที่แตกต่างกันสำหรับแต่ละเธรด

private ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public String formatDate(Date d) {
    SimpleDateFormat sdf = tl.get();
    if(sdf == null) {
        sdf = new SimpleDateFormat("yyyy-MM-hh");
        tl.set(sdf);
    }
    return sdf.format(d);
}

10
+1 สำหรับการแจ้งปัญหานี้ หลายคนคิดว่า SimpleDateFormat เป็นเธรดที่ปลอดภัย (ฉันเห็นสมมติฐานทุกที่)
Adam Gent

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับแนวทาง ThreadLocal โปรดดู: javaspecialists.eu/archive/Issue172.html
miner49r

และสำหรับเหตุผลดูคำถามนี้stackoverflow.com/questions/6840803/…
Raedwald

@ 3urdoch คุณข้ามคีย์เวิร์ด 'คงที่' ใน Option-2 ผิดพลาดหรือไม่?
M Faisal Hameed

คำตอบ:


43
  1. สร้าง SimpleDateFormat คือมีราคาแพง อย่าใช้สิ่งนี้เว้นแต่จะทำไม่ค่อยเสร็จ

  2. ตกลงถ้าคุณอยู่ได้ด้วยการปิดกั้นเล็กน้อย ใช้ถ้า formatDate () ไม่ได้ใช้มาก

  3. ตัวเลือกที่เร็วที่สุดหากคุณใช้เธรดซ้ำ ( เธรดพูล ) ใช้หน่วยความจำมากกว่า 2 และมีค่าใช้จ่ายในการเริ่มต้นที่สูงกว่า

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

สำหรับไลบรารีที่บุคคลที่สามจะใช้ฉันจะใช้ตัวเลือก 3


หากเราใช้ Option-2 และประกาศSimpleDateFormatเป็นตัวแปรอินสแตนซ์เราสามารถใช้synchronized blockเพื่อทำให้เธรดปลอดภัยได้ แต่แสดงให้เห็นว่าโซนาร์เตือนปลาหมึก AS2885 มีวิธีใดในการแก้ไขปัญหาโซนาร์หรือไม่?
M Faisal Hameed

24

ตัวเลือกอื่นคือ Commons Lang FastDateFormatแต่คุณสามารถใช้ได้เฉพาะกับการจัดรูปแบบวันที่และไม่แยกวิเคราะห์

แตกต่างจาก Joda ตรงที่สามารถทำหน้าที่แทนดรอปอินสำหรับการจัดรูปแบบได้ (อัปเดต: ตั้งแต่ v3.3.2 FastDateFormat สามารถสร้างFastDateParserซึ่งเป็นการทดแทนเธรดที่ปลอดภัยสำหรับ SimpleDateFormat)


8
เนื่องจาก Commons Lang 3.2 FastDateFormatมีparse()วิธีการเช่นกัน
manuna

20

หากคุณใช้ Java 8 คุณอาจต้องการใช้java.time.format.DateTimeFormatter:

คลาสนี้ไม่เปลี่ยนรูปและปลอดภัยต่อเกลียว

เช่น:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String str = new java.util.Date().toInstant()
                                 .atZone(ZoneId.systemDefault())
                                 .format(formatter);

6

ตอนนี้ Commons Lang 3.x มี FastDateParser และ FastDateFormat เธรดปลอดภัยและเร็วกว่า SimpleDateFormat นอกจากนี้ยังใช้ข้อกำหนดรูปแบบ / รูปแบบการแยกวิเคราะห์เดียวกันกับ SimpleDateFormat


ใช้ได้เฉพาะใน
3.2+

4

อย่าใช้ SimpleDateFormat ให้ใช้ DateTimeFormatter ของ joda-time แทน มันค่อนข้างเข้มงวดกว่าในด้านการแยกวิเคราะห์ดังนั้นจึงไม่ได้มีการแทนที่ SimpleDateFormat แต่ joda-time เป็นมิตรต่อกันมากขึ้นในแง่ของความปลอดภัยและประสิทธิภาพ


3

ฉันจะบอกว่าให้สร้าง wrapper-class อย่างง่ายสำหรับ SimpleDateFormat ที่ซิงโครไนซ์การเข้าถึง parse () และ format () และสามารถใช้เป็นการแทนที่แบบดรอปอินได้ เข้าใจผิดได้มากกว่าตัวเลือก # 2 ของคุณยุ่งยากน้อยกว่าตัวเลือก # 3 ของคุณ

ดูเหมือนว่าการทำให้ SimpleDateFormat ไม่ซิงโครไนซ์เป็นการตัดสินใจที่ไม่ดีในส่วนของนักออกแบบ Java API ฉันสงสัยว่ามีใครคาดหวังว่า format () และ parse () จะต้องซิงโครไนซ์


1

อีกทางเลือกหนึ่งคือเก็บอินสแตนซ์ไว้ในคิวเธรดที่ปลอดภัย:

import java.util.concurrent.ArrayBlockingQueue;
private static final int DATE_FORMAT_QUEUE_LEN = 4;
private static final String DATE_PATTERN = "yyyy-MM-dd HH:mm:ss";
private ArrayBlockingQueue<SimpleDateFormat> dateFormatQueue = new ArrayBlockingQueue<SimpleDateFormat>(DATE_FORMAT_QUEUE_LEN);
// thread-safe date time formatting
public String format(Date date) {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    String text = fmt.format(date);
    dateFormatQueue.offer(fmt);
    return text;
}
public Date parse(String text) throws ParseException {
    SimpleDateFormat fmt = dateFormatQueue.poll();
    if (fmt == null) {
        fmt = new SimpleDateFormat(DATE_PATTERN);
    }
    Date date = null;
    try {
        date = fmt.parse(text);
    } finally {
        dateFormatQueue.offer(fmt);
    }
    return date;
}

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


1

ฉันเพิ่งใช้สิ่งนี้กับตัวเลือก 3 แต่ได้ทำการเปลี่ยนแปลงโค้ดเล็กน้อย:

  • โดยปกติแล้ว ThreadLocal ควรเป็นแบบคงที่
  • ดูเหมือนจะสะอาดกว่าที่จะแทนที่ initialValue () แทนที่จะทดสอบว่า (get () == null)
  • คุณอาจต้องการตั้งค่าโลแคลและโซนเวลาเว้นแต่ว่าคุณต้องการการตั้งค่าเริ่มต้นจริงๆ (ค่าเริ่มต้นมักจะเกิดข้อผิดพลาดกับ Java)

    private static final ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-hh", Locale.US);
            sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
            return sdf;
        }
    };
    public String formatDate(Date d) {
        return tl.get().format(d);
    }
    

0

ลองนึกภาพแอปพลิเคชันของคุณมีเธรดเดียว ทำไมคุณจึงซิงโครไนซ์การเข้าถึงตัวแปร SimpleDataFormat แล้ว?

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.