[英]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直接替換嗎?
有沒有更好的替代方法來使這個線程安全?
不要使用字符串,而是使用通過 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類,這些類多年前取代了麻煩的舊日期時間類,例如SimpleDateFormat
、 java.util.Date
、 java.sql.Date
和Calendar
。
java.time類被設計為線程安全的。 他們使用不可變對象模式,根據原始值返回新對象,而不是“變異”(改變)原始對象。
我認為沒有理由在您的示例代碼中使用字符串:不在您的數據庫訪問代碼中,不在您的業務對象 ( Trade
) 中。
從 JDBC 4.2 開始,我們可以與數據庫交換java.time對象。 對於類似於 SQL 標准DATE
類型的數據庫列,請使用類LocalDate
。 LocalDate
類表示沒有時間和時區的僅日期值。
myPreparedStatement.setObject( … , myLocalDate ) ;
恢復。
LocalDate myLocalDate = myResultSet.getObject( … , LocalDate.class ) ;
您的Trade
類應該將成員變量startDate
和endDate
作為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 8 及更高版本中。 這些類取代麻煩的老傳統日期時間類,如java.util.Date
, Calendar
,和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 的試驗場。 您可以在這里找到一些有用的類,比如Interval
, YearWeek
, YearQuarter
,和更多。
您可以將其ThreadLocal
。 池中的每個線程都將擁有自己的格式化程序。
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("dd-MMM-yyyy");
}
};
在這里您可以看到以線程安全的方式使用日期格式的最快方法。 因為您有 3 種方法可以做到:
DateFormat.getDateInstance()
synchronized
完整代碼示例:
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.