Java 使SimpleDataFormat线程安全

Java 使SimpleDataFormat线程安全,java,multithreading,thread-safety,simpledateformat,Java,Multithreading,Thread Safety,Simpledateformat,我有许多线程处理Trade对象,其中我使用RowMapper将数据库列映射到Trade对象 我知道在任何Java中,SimpleDataFormat都不是线程安全的。因此,我在startDate中得到了一些不可预测的结果。例如,我在startDate中也看到了日期endDate 这是我的密码: public class ExampleTradeMapper { private static final SimpleDateFormat DATE_FORMAT = new SimpleD

我有许多线程处理
Trade
对象,其中我使用
RowMapper
将数据库列映射到
Trade
对象

我知道在任何Java中,
SimpleDataFormat
都不是线程安全的。因此,我在
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;
    }
}
公共类示例TradeMapper{
私有静态最终SimpleDataFormat日期\格式=新SimpleDataFormat(“dd MMM yyyy”);
public void映射(Trade、ResultSet rs、int rowNum)抛出SQLException{
交易开始日期(getFormattedDate(rs.getDate(“开始日期”));
trade.setEndDate(getFormattedDate(rs.getDate(“结束日期”));
trade.setDescription(rs.getString(“DESCRIPTION”);
}
私有字符串getFormattedDate(日期){
试一试{
如果(日期!=null)
返回日期_FORMAT.FORMAT(DATE).toUpperCase();
其他的
返回null;
}捕获(例外e){
e、 printStackTrace();
}
返回null;
}
}
公共类SomeRowMapper扩展TradeMapper实现RowMapper{
@凌驾
public Trade mapRow(结果集rs,int rowNum)引发SQLException{
贸易=新贸易();
地图(贸易、遥感、rowNum);
退货贸易;
}
}
对于这个应用程序,我的核心池大小约为20,最大约为50。这些线程可以在某个时间处理来自数据库的大约100条交易记录

使此日期格式化线程安全的最佳方法是什么?我是否应该使用直接替换

有没有更好的替代方法来确保线程安全

你能做到。池中的每个线程都将拥有自己的格式化程序

private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("dd-MMM-yyyy");
    }
};
private static final ThreadLocal DATE\u FORMAT=new ThreadLocal(){
@凌驾
受保护的SimpleDataFormat初始值(){
返回新的SimpleDataFormat(“dd-MMM-yyyy”);
}
};
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对象,以便跨线程使用

java.time 使SimpleDataFormat线程安全

不要

使用几年前取代了麻烦的旧遗留日期时间类的现代java.time类,如
SimpleDateFormat
java.util.date
java.sql.date
Calendar

time类被设计成线程安全的。它们使用模式,根据原始对象的值返回新对象,而不是“修改”(mutation)原始对象

使用智能对象,而不是哑字符串 我看不出在示例代码中使用字符串的理由:不在数据库访问代码中,也不在业务对象(
Trade
)中

JDBC 从JDBC4.2开始,我们可以与数据库交换java.time对象。对于类似于SQL标准
DATE
类型的数据库列,请使用类
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() …
    }
…
}
不需要字符串,也不需要格式化模式

串 要将日期时间值交换或存储为文本,请使用标准格式,而不是问题中显示的自定义格式

在解析/生成字符串时,java.time类默认使用ISO 8601格式。因此,无需指定格式化模式

LocalDate ld = LocalDate.parse( "2018-01-23" ) ; // January 23, 2018.
String s = ld.toString() ;  // Outputs 2018-01-23. 
对于用户界面中的表示,让java.time自动本地化。要本地化,请指定:

  • 确定字符串的长度或缩写
  • 确定:
    • 翻译日名、月名等的人类语言
    • 决定缩写、大写、标点、分隔符等问题的文化规范
例如:

Locale l = Locale.CANADA_FRENCH ; 
DateTimeFormatter f = 
    DateTimeFormatter.ofLocalizedDate( FormatStyle.FULL )
                     .withLocale( l ) ;
String output = ld.format( f ) ;
2018年1月23日

根据设计,
DateTimeFormatter
类作为不可变对象是线程安全的。您可以保存一个实例以跨线程使用


关于java.time 该框架内置于Java8及更高版本中。这些类取代了麻烦的旧日期时间类,例如,&

该项目现已启动,建议迁移到类

要了解更多信息,请参阅。并搜索堆栈溢出以获得许多示例和解释。规格是

您可以直接与数据库交换java.time对象。使用兼容的或更高版本。不需要字符串,也不需要
java.sql.*

从哪里获得java.time类

  • 然后
    • 内置的
    • 标准JavaAPI的一部分,带有捆绑实现
    • Java9添加了一些次要功能和修复
    • 大部分java.time功能都在中向后移植到Java6和Java7
    • 更高版本的Android捆绑包实现了java.time类

    • 对于早期的Android(),您可以在这里看到以线程安全方式使用日期格式的最快方法。因为您有3种方法:

    • 使用
      DateFormat.getDateInstance()
    • 同步
    • 还有本地线程方式,它提供了最好的性能
    • 完整代码示例:

      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);
              }
      
          }
      
      }
      
      导入java.text.DateFormat;
      导入java.text.ParseException;
      导入java.text.simpleDataFormat;
      导入java.util.Date;
      导入java.util.concurrent.ExecutorService;
      导入java.util