Sinkronisasi akses ke SimpleDateFormat

91

Javadoc untuk SimpleDateFormat menyatakan bahwa SimpleDateFormat tidak disinkronkan.

"Format tanggal tidak disinkronkan. Disarankan untuk membuat contoh format terpisah untuk setiap utas. Jika beberapa utas mengakses format secara bersamaan, itu harus disinkronkan secara eksternal."

Tapi apa pendekatan terbaik untuk menggunakan instance SimpleDateFormat di lingkungan multi threaded. Berikut adalah beberapa opsi yang telah saya pikirkan, saya telah menggunakan opsi 1 dan 2 di masa lalu, tetapi saya ingin tahu apakah ada alternatif yang lebih baik atau opsi mana yang menawarkan kinerja dan konkurensi terbaik.

Opsi 1: Buat instance lokal saat diperlukan

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

Opsi 2: Buat instance SimpleDateFormat sebagai variabel kelas tetapi akses sinkronisasinya.

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

Opsi 3: Buat ThreadLocal untuk menyimpan instance SimpleDateFormat yang berbeda untuk setiap utas.

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);
}
3urdoch
sumber
10
1 untuk mengangkat masalah ini. Banyak orang berpikir bahwa SimpleDateFormat adalah thread safe (saya melihat asumsi di mana-mana).
Adam Gent
Untuk informasi lebih lanjut tentang pendekatan ThreadLocal, lihat: javaspecialists.eu/archive/Issue172.html
miner49r
Dan untuk alasannya, lihat pertanyaan ini: stackoverflow.com/questions/6840803/…
Raedwald
@ 3urdoch Apakah Anda melewatkan kata kunci 'statis' secara keliru dalam Opsi-2?
M Faisal Hameed

Jawaban:

43
  1. Membuat SimpleDateFormat itu mahal . Jangan gunakan ini kecuali jarang dilakukan.

  2. OK jika Anda bisa hidup dengan sedikit pemblokiran. Gunakan jika formatDate () tidak banyak digunakan.

  3. Opsi tercepat JIKA Anda menggunakan kembali utas ( kumpulan utas ). Menggunakan lebih banyak memori daripada 2. dan memiliki overhead startup yang lebih tinggi.

Untuk aplikasi, 2. dan 3. adalah opsi yang memungkinkan. Yang terbaik untuk kasus Anda tergantung pada kasus penggunaan Anda. Waspadai pengoptimalan prematur. Lakukan hanya jika Anda yakin ini merupakan masalah.

Untuk perpustakaan yang akan digunakan oleh pihak ke-3, saya akan menggunakan opsi 3.

Peter Knego
sumber
Jika kita menggunakan Option-2 dan mendeklarasikan SimpleDateFormatsebagai variabel instan, maka kita dapat menggunakannya synchronized blockuntuk membuatnya aman bagi thread. Tapi sonar menunjukkan peringatan cumi-cumi-AS2885 . Apakah ada cara untuk mengatasi masalah sonar?
M Faisal Hameed
24

Opsi lainnya adalah Commons Lang FastDateFormat tetapi Anda hanya dapat menggunakannya untuk pemformatan tanggal dan bukan parsing.

Tidak seperti Joda, ini dapat berfungsi sebagai pengganti drop-in untuk pemformatan. (Pembaruan: Sejak v3.3.2, FastDateFormat dapat menghasilkan FastDateParser , yang merupakan pengganti thread-safe untuk SimpleDateFormat)

Adam Gent
sumber
8
Sejak Commons Lang 3.2, FastDateFormatmemiliki parse()metode juga
manuna
20

Jika Anda menggunakan Java 8, Anda mungkin ingin menggunakan java.time.format.DateTimeFormatter:

Kelas ini tidak dapat diubah dan aman untuk thread.

misalnya:

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

Commons Lang 3.x sekarang memiliki FastDateParser serta FastDateFormat. Ini aman untuk benang dan lebih cepat dari SimpleDateFormat. Ini juga menggunakan spesifikasi pola format / parse yang sama seperti SimpleDateFormat.

Chas
sumber
Ini hanya tersedia di 3.2+ dan bukan 3.x
Wisteso
4

Jangan gunakan SimpleDateFormat, gunakan DateTimeFormatter joda-time sebagai gantinya. Ini sedikit lebih ketat di sisi penguraian dan jadi bukan penurunan yang cukup untuk menggantikan SimpleDateFormat, tetapi joda-time jauh lebih ramah secara bersamaan dalam hal keamanan dan kinerja.

Jed Wesley-Smith
sumber
3

Saya akan mengatakan, buat kelas pembungkus sederhana untuk SimpleDateFormat yang menyinkronkan akses ke parse () dan format () dan dapat digunakan sebagai pengganti drop-in. Lebih aman daripada opsi # 2 Anda, lebih praktis daripada opsi # 3 Anda.

Sepertinya membuat SimpleDateFormat tidak tersinkronisasi adalah keputusan desain yang buruk di pihak desainer Java API; Saya ragu ada yang mengharapkan format () dan parse () perlu disinkronkan.

Andy
sumber
1

Opsi lainnya adalah menyimpan instance dalam antrian thread-safe:

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

Ukuran dateFormatQueue harus mendekati perkiraan jumlah utas yang dapat memanggil fungsi ini secara rutin pada waktu yang sama. Dalam kasus terburuk di mana lebih banyak utas daripada jumlah ini benar-benar menggunakan semua contoh secara bersamaan, beberapa contoh SimpleDateFormat akan dibuat yang tidak dapat dikembalikan ke dateFormatQueue karena penuh. Ini tidak akan menghasilkan kesalahan, itu hanya akan menimbulkan hukuman karena membuat beberapa SimpleDateFormat yang hanya digunakan sekali.

SamJ
sumber
1

Saya baru saja menerapkan ini dengan Opsi 3, tetapi membuat beberapa perubahan kode:

  • ThreadLocal biasanya harus statis
  • Tampaknya lebih bersih untuk mengganti initialValue () daripada menguji if (get () == null)
  • Anda mungkin ingin mengatur lokal dan zona waktu kecuali Anda benar-benar menginginkan pengaturan default (default sangat rawan kesalahan dengan 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);
    }
    
mwk
sumber
0

Bayangkan aplikasi Anda memiliki satu utas. Mengapa Anda menyinkronkan akses ke variabel SimpleDataFormat?

Pusaran
sumber