Đồng bộ hóa quyền truy cập vào SimpleDateFormat


90

Javadoc cho SimpleDateFormat nói rằng SimpleDateFormat không được đồng bộ hóa.

"Các định dạng ngày không được đồng bộ hóa. Bạn nên tạo các phiên bản định dạng riêng biệt cho từng chuỗi. Nếu nhiều chuỗi truy cập đồng thời vào một định dạng, thì định dạng đó phải được đồng bộ hóa bên ngoài."

Nhưng đâu là cách tốt nhất để sử dụng một phiên bản của SimpleDateFormat trong môi trường đa luồng. Dưới đây là một vài lựa chọn mà tôi đã nghĩ đến, trước đây tôi đã sử dụng các tùy chọn 1 và 2 nhưng tôi tò mò muốn biết liệu có lựa chọn thay thế nào tốt hơn hoặc tùy chọn nào trong số này sẽ mang lại hiệu suất và tính đồng thời tốt nhất.

Tùy chọn 1: Tạo phiên bản cục bộ khi được yêu cầu

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

Tùy chọn 2: Tạo một thể hiện của SimpleDateFormat như một biến lớp nhưng đồng bộ hóa quyền truy cập vào nó.

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

Tùy chọn 3: Tạo một ThreadLocal để lưu trữ một phiên bản SimpleDateFormat khác nhau cho mỗi luồng.

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 vì đã nêu vấn đề này. Vì vậy, nhiều người nghĩ rằng SimpleDateFormat là luồng an toàn (tôi thấy các giả định ở khắp mọi nơi).
Adam Gent

Để biết thêm thông tin về cách tiếp cận ThreadLocal, hãy xem: javaspecialists.eu/archive/Issue172.html
miner49r

Và tại sao, nhìn thấy câu hỏi này: stackoverflow.com/questions/6840803/...
Raedwald

@ 3urdoch Có phải bạn đã bỏ qua từ khóa 'tĩnh' trong Tùy chọn 2 không?
M Faisal Hameed

Câu trả lời:


43
  1. Tạo SimpleDateFormat rất tốn kém . Đừng sử dụng điều này trừ khi nó được thực hiện hiếm khi.

  2. OK nếu bạn có thể sống với một chút chặn. Sử dụng nếu formatDate () không được sử dụng nhiều.

  3. Tùy chọn nhanh nhất NẾU bạn sử dụng lại các chủ đề (nhóm chủ đề ). Sử dụng nhiều bộ nhớ hơn 2. và có chi phí khởi động cao hơn.

Đối với các ứng dụng cả 2. và 3. đều là những lựa chọn khả thi. Cái nào tốt nhất cho trường hợp của bạn phụ thuộc vào trường hợp sử dụng của bạn. Cẩn thận với việc tối ưu hóa quá sớm. Chỉ làm điều đó nếu bạn tin rằng đây là một vấn đề.

Đối với các thư viện sẽ được bên thứ 3 sử dụng, tôi sẽ sử dụng tùy chọn 3.


Nếu chúng ta sử dụng Option-2 và khai báo SimpleDateFormatnhư một biến thể hiện thì chúng ta có thể sử dụng synchronized blockđể làm cho nó an toàn theo luồng. Nhưng sonar hiển thị cảnh báo mực-AS2885 . Có cách nào để giải quyết vấn đề sonar không?
M Faisal Hameed

24

Tùy chọn khác là Commons Lang FastDateFormat nhưng bạn chỉ có thể sử dụng nó để định dạng ngày chứ không phải phân tích cú pháp.

Không giống như Joda, nó có thể hoạt động như một phần mềm thay thế cho định dạng. (Cập nhật: Kể từ v3.3.2, FastDateFormat có thể tạo ra FastDateParser , đây là một sự thay thế an toàn trong chuỗi cho SimpleDateFormat)


8
Kể từ Commons Lang 3.2, FastDateFormatcũng có parse()phương pháp
manuna

19

Nếu bạn đang sử dụng Java 8, bạn có thể muốn sử dụng java.time.format.DateTimeFormatter:

Lớp này là bất biến và an toàn theo luồng.

ví dụ:

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

6

Commons Lang 3.x hiện có FastDateParser cũng như FastDateFormat. Nó an toàn và nhanh hơn SimpleDateFormat. Nó cũng sử dụng các thông số định dạng / phân tích cú pháp tương tự như SimpleDateFormat.


Nó chỉ có sẵn ở 3.2+ chứ không phải 3.x
Wisteso

4

Không sử dụng SimpleDateFormat, hãy sử dụng DateTimeFormatter của joda-time thay thế. Nó nghiêm ngặt hơn một chút trong khía cạnh phân tích cú pháp và do đó không phải là sự thay thế cho SimpleDateFormat hoàn toàn, nhưng joda-time thân thiện hơn nhiều về mặt an toàn và hiệu suất.


3

Tôi có thể nói, hãy tạo một lớp bao bọc đơn giản cho SimpleDateFormat để đồng bộ hóa quyền truy cập vào phân tích cú pháp () và định dạng () và có thể được sử dụng như một thay thế thả vào. Dễ hiểu hơn tùy chọn số 2 của bạn, ít rườm rà hơn so với tùy chọn số 3.

Có vẻ như việc làm cho SimpleDateFormat không được đồng bộ hóa là một quyết định thiết kế kém của các nhà thiết kế Java API; Tôi nghi ngờ có ai đó mong muốn định dạng () và phân tích cú pháp () cần được đồng bộ hóa.


1

Một tùy chọn khác là giữ các phiên bản trong hàng đợi an toàn theo chuỗi:

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

Kích thước của dateFormatQueue phải gần với số luồng ước tính có thể thường xuyên gọi hàm này cùng một lúc. Trong trường hợp xấu nhất khi nhiều luồng hơn số này thực sự sử dụng đồng thời tất cả các trường hợp, một số trường hợp SimpleDateFormat sẽ được tạo và không thể trả về dateFormatQueue vì nó đã đầy. Điều này sẽ không tạo ra lỗi, nó sẽ chỉ bị phạt khi tạo một số SimpleDateFormat chỉ được sử dụng một lần.


1

Tôi vừa triển khai điều này với Tùy chọn 3, nhưng đã thực hiện một số thay đổi mã:

  • ThreadLocal thường phải tĩnh
  • Có vẻ sạch sẽ hơn để ghi đè initialValue () thay vì kiểm tra if (get () == null)
  • Bạn có thể muốn đặt ngôn ngữ và múi giờ trừ khi bạn thực sự muốn cài đặt mặc định (mặc định rất dễ xảy ra lỗi với 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

Hãy tưởng tượng ứng dụng của bạn có một luồng. Tại sao bạn sẽ đồng bộ hóa quyền truy cập vào biến SimpleDataFormat sau đó?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.