簡體   English   中英

使 SimpleDateFormat 線程安全

[英]Making SimpleDateFormat thread safe

我有很多線程處理Trade對象,我使用RowMapper將數據庫列映射到Trade對象。

我知道SimpleDateFormat在任何 Java 中都不是線程安全的。 結果,我在startDate得到了一些不可預測的結果。 例如,我在startDate也看到了endDate日期。

這是我的代碼:

public class ExampleTradeMapper {

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("dd-MMM-yyyy");

    public void map(Trade trade, ResultSet rs, int rowNum) throws SQLException {    

        trade.setStartDate(getFormattedDate(rs.getDate("START_DATE")));
        trade.setEndDate(getFormattedDate(rs.getDate("END_DATE")));
        trade.setDescription(rs.getString("DESCRIPTION"));

    }

    private String getFormattedDate(Date date) {
        try {
            if (date != null)
                return DATE_FORMAT.format(date).toUpperCase();
            else
                return null;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}


public class SomeRowMapper extends TradeMapper implements RowMapper<Trade> {

    @Override
    public Trade mapRow(ResultSet rs, int rowNum) throws SQLException {

        Trade trade = new Trade();

        map(trade, rs, rowNum);

        return trade;
    }
}

對於這個應用程序,我的核心池大小約為 20,最大約為 50。這些線程有時可以處理來自數據庫的大約 100 條交易記錄。

使此日期格式化線程安全的最佳方法是什么? 我應該使用FastDateFormat直接替換嗎?

有沒有更好的替代方法來使這個線程安全?

tl;博士

不要使用字符串,而是使用通過 JDBC 4.2 或更高版本與數據庫交換的java.time對象(特別是LocalDate )。

myResultSet.getObject(      // Exchange modern java.time objects with your database.
    "START_DATE" ,
    LocalDate.class 
)                           // Returns a `LocalDate` object.
.format(                    // Generate a `String` representing textually the content of this `LocalDate`. 
    DateTimeFormatter.ofPattern( "dd-MMM-uuuu" , Locale.US )
)

2018 年 1 月 23 日

作為不可變對象, java.time對象在設計上是線程安全的。 您可以緩存java.time對象以跨線程使用。

時間

使 SimpleDateFormat 線程安全

別。

使用現代java.time類,這些類多年前取代了麻煩的舊日期時間類,例如SimpleDateFormatjava.util.Datejava.sql.DateCalendar

java.time類被設計為線程安全的。 他們使用不可變對象模式,根據原始值返回新對象,而不是“變異”(改變)原始對象。

使用智能對象,而不是啞弦

我認為沒有理由在您的示例代碼中使用字符串:不在您的數據庫訪問代碼中,不在您的業務對象 ( Trade ) 中。

JDBC

從 JDBC 4.2 開始,我們可以與數據庫交換java.time對象。 對於類似於 SQL 標准DATE類型的數據庫列,請使用類LocalDate LocalDate類表示沒有時間和時區的僅日期值。

myPreparedStatement.setObject( … , myLocalDate ) ;

恢復。

LocalDate myLocalDate = myResultSet.getObject( … , LocalDate.class ) ;

業務對象

您的Trade類應該將成員變量startDateendDate作為LocalDate對象,而不是字符串。

public class Trade {
    private LocalDate startDate ;
    private LocalDate endDate ;
    … 

    // Getters
    public LocalDate getStartDate() { 
        return this.startDate ;
    }
    public LocalDate getEndDate() { 
        return this.endDate;
    }
    public Period getPeriod() {  // Number of years-months-days elapsed.
        return Period.between( this.startDate , this.endDate ) ;
    }

    // Setters
    public void setStartDate( LocalDate startDateArg ) { 
        this.startDate = startDateArg ;
    }
    public void setEndDate( LocalDate endDateArg ) { 
        this.endDate = endDateArg ;
    }

    @Override
    public toString() {
        "Trade={ " + "startDate=" + this.startDate.toString() …
    }
…
}

不需要字符串,不需要格式化模式。

字符串

要將日期時間值交換或存儲為文本,請使用標准ISO 8601格式而不是您的問題中看到的自定義格式。

java.time類在解析/生成字符串時默認使用 ISO 8601 格式。 所以不需要指定格式模式。

LocalDate ld = LocalDate.parse( "2018-01-23" ) ; // January 23, 2018.
String s = ld.toString() ;  // Outputs 2018-01-23. 

為了在用戶界面中呈現,讓java.time自動本地化。 要本地化,請指定:

  • FormatStyle確定字符串的長度或縮寫。
  • Locale確定:
    • 用於翻譯日名、月名等的人類語言
    • 決定縮寫、大寫、標點符號、分隔符等問題的文化規范

例子:

Locale l = Locale.CANADA_FRENCH ; 
DateTimeFormatter f = 
    DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL )
                     .withLocale( l ) ;
String output = ld.format( f ) ;

狂歡節 2018 年 1 月 23 日

DateTimeFormatter類在設計上是線程安全的,作為不可變對象。 您可以持有一個跨線程使用的實例。


關於java.time

java.time框架內置於 Java 8 及更高版本中。 這些類取代麻煩的老傳統日期時間類,如java.util.DateCalendar ,和SimpleDateFormat

現在處於維護模式Joda-Time項目建議遷移到java.time類。

要了解更多信息,請參閱Oracle 教程 並在 Stack Overflow 上搜索許多示例和解釋。 規范是JSR 310

您可以直接與您的數據庫交換java.time對象。 使用符合JDBC 4.2或更高版本的JDBC 驅動程序 不需要字符串,不需要java.sql.*類。

從哪里獲得 java.time 類?

ThreeTen-Extra項目用額外的類擴展了 java.time。 該項目是未來可能添加到 java.time 的試驗場。 您可以在這里找到一些有用的類,比如IntervalYearWeekYearQuarter ,和更多

您可以將其ThreadLocal 池中的每個線程都將擁有自己的格式化程序。

private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("dd-MMM-yyyy");
    }
};

在這里您可以看到以線程安全的方式使用日期格式的最快方法。 因為您有 3 種方法可以做到:

  1. 使用DateFormat.getDateInstance()
  2. synchronized
  3. 以及遠距離提供最佳性能的本地線程方式

完整代碼示例:

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleDateFormatThreadExample {

    private static String FORMAT = "dd-M-yyyy hh:mm:ss";

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT);

    public static void main(String[] args) {

        final String dateStr = "02-1-2018 06:07:59";

        ExecutorService executorService = Executors.newFixedThreadPool(10);

        Runnable task = new Runnable() {

            @Override
            public void run() {
                parseDate(dateStr);
            }

        };

        Runnable taskInThread = new Runnable() {

            @Override
            public void run() {
                try {
                    ConcurrentDateFormatAccess concurrentDateFormatAccess = new ConcurrentDateFormatAccess();
                    System.out.println("Successfully Parsed Date " + concurrentDateFormatAccess.convertStringToDate(dateStr));
                    // don't forget to use CLEAN because the classloader with keep date format !
                    concurrentDateFormatAccess.clean();
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }

        };

        for (int i = 0; i < 100; i++) {
            executorService.submit(task);
            // remove this comment to use thread safe way !
            // executorService.submit(taskInThread);
        }
        executorService.shutdown();
    }

    private static void parseDate(String dateStr) {
        try {
            Date date = simpleDateFormat.parse(dateStr);
            System.out.println("Successfully Parsed Date " + date);
        } catch (ParseException e) {
            System.out.println("ParseError " + e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class ConcurrentDateFormatAccess {

        private ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {

            @Override
            public DateFormat get() {
                return super.get();
            }

            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat(FORMAT);
            }

            @Override
            public void remove() {
                super.remove();
            }

            @Override
            public void set(DateFormat value) {
                super.set(value);
            }

        };

        public void clean() {
            df.remove();
        }

        public Date convertStringToDate(String dateString) throws ParseException {
            return df.get().parse(dateString);
        }

    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM