简体   繁体   English

使 SimpleDateFormat 线程安全

[英]Making SimpleDateFormat thread safe

I have many threads processing Trade objects where I use a RowMapper to map database columns to Trade object.我有很多线程处理Trade对象,我使用RowMapper将数据库列映射到Trade对象。

I understand SimpleDateFormat is not thread-safe in any Java.我知道SimpleDateFormat在任何 Java 中都不是线程安全的。 As a result, I get some unpredictable result in startDate .结果,我在startDate得到了一些不可预测的结果。 For example, I see date which is endDate also in startDate .例如,我在startDate也看到了endDate日期。

Here is my code:这是我的代码:

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

My core pool size is about 20 for this application with maximum about 50. These threads can be processing about 100s of trade records from the database at some time.对于这个应用程序,我的核心池大小约为 20,最大约为 50。这些线程有时可以处理来自数据库的大约 100 条交易记录。

What would be the best way to make this date formatting thread safe?使此日期格式化线程安全的最佳方法是什么? Should I be using a direct replacement using FastDateFormat ?我应该使用FastDateFormat直接替换吗?

Is there a better alternative way of doing making this thread safe?有没有更好的替代方法来使这个线程安全?

tl;dr tl;博士

Rather than using strings, use java.time objects ( LocalDate specifically) exchanged with your database via JDBC 4.2 or later.不要使用字符串,而是使用通过 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 )
)

23-Jan-2018 2018 年 1 月 23 日

Being immutable objects, the java.time objects are thread-safe by design.作为不可变对象, java.time对象在设计上是线程安全的。 You can cache the java.time objects for use across threads.您可以缓存java.time对象以跨线程使用。

java.time时间

Making SimpleDateFormat thread safe使 SimpleDateFormat 线程安全

Don't.别。

Use the modern java.time classes that years ago supplanted the troublesome old legacy date-time classes such as SimpleDateFormat , java.util.Date , java.sql.Date , and Calendar .使用现代java.time类,这些类多年前取代了麻烦的旧日期时间类,例如SimpleDateFormatjava.util.Datejava.sql.DateCalendar

The java.time classes are designed to be thread-safe. java.time类被设计为线程安全的。 They use immutable objects pattern, to return fresh objects based on the values of an original rather than “mutating” (altering) the original.他们使用不可变对象模式,根据原始值返回新对象,而不是“变异”(改变)原始对象。

Use smart objects, not dumb strings使用智能对象,而不是哑弦

I see no reason for using strings in your example code: Not in your database access code, not in your business object ( Trade ).我认为没有理由在您的示例代码中使用字符串:不在您的数据库访问代码中,不在您的业务对象 ( Trade ) 中。

JDBC JDBC

As of JDBC 4.2, we can exchange java.time objects with the database.从 JDBC 4.2 开始,我们可以与数据库交换java.time对象。 For a database column of a type akin to the SQL-standard DATE , use the class LocalDate .对于类似于 SQL 标准DATE类型的数据库列,请使用类LocalDate The LocalDate class represents a date-only value without time-of-day and without time zone. LocalDate类表示没有时间和时区的仅日期值。

myPreparedStatement.setObject( … , myLocalDate ) ;

Retrieval.恢复。

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

Business object业务对象

Your Trade class should be holding member variables startDate & endDate as LocalDate objects, not strings.您的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() …
    }
…
}

No need for strings, no need for formatting patterns.不需要字符串,不需要格式化模式。

Strings字符串

To exchange or store date-time values as text, use the standard ISO 8601 formats rather than a custom format as seen in your Question.要将日期时间值交换或存储为文本,请使用标准ISO 8601格式而不是您的问题中看到的自定义格式。

The java.time classes use the ISO 8601 formats by default when parsing/generating strings. java.time类在解析/生成字符串时默认使用 ISO 8601 格式。 So no need to specify a formatting pattern.所以不需要指定格式模式。

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

For presentation in a user-interface, let java.time automatically localize.为了在用户界面中呈现,让java.time自动本地化。 To localize, specify:要本地化,请指定:

  • FormatStyle to determine how long or abbreviated should the string be. FormatStyle确定字符串的长度或缩写。
  • Locale to determine: Locale确定:
    • The human language for translation of name of day, name of month, and such.用于翻译日名、月名等的人类语言
    • The cultural norms deciding issues of abbreviation, capitalization, punctuation, separators, and such.决定缩写、大写、标点符号、分隔符等问题的文化规范

Example:例子:

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

mardi 23 janvier 2018狂欢节 2018 年 1 月 23 日

The DateTimeFormatter class is thread-safe, by design, as an immutable object. DateTimeFormatter类在设计上是线程安全的,作为不可变对象。 You could hold one instance to be used across threads.您可以持有一个跨线程使用的实例。


About java.time关于java.time

The java.time framework is built into Java 8 and later. java.time框架内置于 Java 8 及更高版本中。 These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat .这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar ,和SimpleDateFormat

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes.现在处于维护模式Joda-Time项目建议迁移到java.time类。

To learn more, see the Oracle Tutorial .要了解更多信息,请参阅Oracle 教程 And search Stack Overflow for many examples and explanations.并在 Stack Overflow 上搜索许多示例和解释。 Specification is JSR 310 .规范是JSR 310

You may exchange java.time objects directly with your database.您可以直接与您的数据库交换java.time对象。 Use a JDBC driver compliant with JDBC 4.2 or later.使用符合JDBC 4.2或更高版本的JDBC 驱动程序 No need for strings, no need for java.sql.* classes.不需要字符串,不需要java.sql.*类。

Where to obtain the java.time classes?从哪里获得 java.time 类?

The ThreeTen-Extra project extends java.time with additional classes. ThreeTen-Extra项目用额外的类扩展了 java.time。 This project is a proving ground for possible future additions to java.time.该项目是未来可能添加到 java.time 的试验场。 You may find some useful classes here such as Interval , YearWeek , YearQuarter , and more .您可以在这里找到一些有用的类,比如IntervalYearWeekYearQuarter ,和更多

You can make it ThreadLocal .您可以将其ThreadLocal Every thread in the pool will hold their own formattor.池中的每个线程都将拥有自己的格式化程序。

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

Here you can see the fastest way to use date format in thread safe way.在这里您可以看到以线程安全的方式使用日期格式的最快方法。 Because you have 3 ways to do it :因为您有 3 种方法可以做到:

  1. With DateFormat.getDateInstance()使用DateFormat.getDateInstance()
  2. With synchronizedsynchronized
  3. And the local thread way which from far offers the best performance以及远距离提供最佳性能的本地线程方式

Full code sample :完整代码示例:

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