11

I want to make following code thread safe. What is the best way to achieve it?

private static final DateFormat DATE_FORMAT = DateFormat.getDateTimeInstance();

public static final String eventTypeToDateTimeString(long timestamp)
{
   return DATE_FORMAT.format(new Date(timestamp));
}
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
Ahmad.Masood
  • 1,289
  • 3
  • 21
  • 40

6 Answers6

12

Avoid the legacy date-time classes

The troublesome old date-time classes bundled with the earliest versions of Java have been supplanted by the java.time classes. The java.time classes are thread-safe and use immutable objects.

java.time

Replace your formatter and date types with java.time types to automatically get thread-safety.

Define your DateTimeFormatter globally if so desired. That class can automatically localize the string being generated, or you can specify a certain format.

  • Specify a FormatStyle to determine length of abbreviation.
  • Specify a Locale to determine (a) the human language for translation of name of day, name of month, and such, and (b) the cultural norms deciding issues of abbreviation, capitalization, punctuation, and such.
  • Specify a ZoneId for a time zone in which to adjust the moment.

The Instant class represents a moment on the timeline in UTC with a resolution of nanoseconds. The ZonedDateTime class adjusts an Instant into a particular time zone.

Your code, translated to java.time classes. In real work I would break this out into multiple lines, and would trap for exceptions.

private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL ).withLocale( Locale.CANADA_FRENCH ) ;
private static final ZoneId ZONE_ID = ZoneId.of( "America/Montreal" );

public static final String eventTypeToDateTimeString(long timestamp)
{
   return Instant.ofEpochMilli( timestamp ).atZone( ZONE_ID ).format( DATE_TIME_FORMATTER );
}

Do not track date-time as count-from-epoch

I do not advise passing around a long as a way of representing date-time values. Makes debugging and logging difficulty as a human cannot discern the meaning of the date-time value. Instead, pass around the java.time types such as Instant. Using the java.time types provides type-safety and makes your code more self-documenting.


About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • The old classes are a mess -- but then so are the new classes. They don't properly implement ISO8601, though they provide a few different half-implementations, with the gaps and behaviour varying between the different overlapping classes. – Daniel Winterstein Aug 14 '18 at 22:18
  • @DanielWinterstein I do not understand your assertion about ISO 8601 not being properly implemented in the *java.time* framework. I suggest you provide specifics, perhaps posting your own Question and Answer to address the points. If you are referring to [Leap Second](https://en.wikipedia.org/wiki/Leap_second) being swallowed, I suggest that is reasonable behavior for general business-oriented apps, keeping calendars synced to the clock. Ignoring the leap via “stop-the-clock” or via [“smearing”](https://developers.google.com/time/smear) is common practice, thereby avoiding any `23:59:60`. – Basil Bourque Aug 15 '18 at 19:16
  • Hello - There are cases where java.time doesn't parse valid and reasonable ISO8601 inputs. I'll dig some out and post them when I have a spare minute... – Daniel Winterstein Aug 30 '18 at 11:29
  • 1
    A bit late. The java.time classes to not include the T between the date and time portions of the time string. The T is part of ISO 8601. – DwB Sep 26 '18 at 19:00
  • @DwB I do not understand your comment. Can you clarify? The `T` between the year-month-day and hour-minute-second portions is specified by the ISO 8601 standard, yes. And the *java.time* classes use the standard [ISO 8601](https://en.m.wikipedia.org/wiki/ISO_8601) formats by default when parsing or generating strings. To generate a standard string, just call `toString` on the various *java.time* classes. Ex: call `Instant.now().toString()` to get standard string: `2018-09-26T21:43:29.500Z` as [seen in IdeOne.com](https://www.ideone.com/ml2JUo). – Basil Bourque Sep 26 '18 at 21:53
  • @DanielWinterstein Please substantiate your claims about *java.time* failing to support ISO 8601, or delete your comments making such allegations as they create [FUD](https://en.wikipedia.org/wiki/Fear%2C_uncertainty_and_doubt) about an important part of Java. – Basil Bourque Sep 26 '18 at 22:05
  • @BasilBourque Try parsing this ISO compliant date time: "2018-01-01T00:00:00+0100" – Daniel Winterstein Sep 27 '18 at 19:04
  • @DanielWinterstein `System.out.println( OffsetDateTime.parse( "2018-01-01T00:00:00+0100" , DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ssX" ) ) );` ➙ `2018-01-01T00:00+01:00` – Basil Bourque Sep 27 '18 at 19:14
  • @BasilBourque Thank you. Though note that `OffsetDateTime.parse("2018-01-01T00:00:00+0100")` will fail. So *java.time* can work -- if you tell it exactly what pattern to use. I think I stand by my earlier comment. To handle any valid ISO string (a reasonable requirement for a good time parser), you have to use a regex or similar to sniff the format. – Daniel Winterstein Sep 28 '18 at 12:14
4

There are several alternatives, with different trade-offs.

You can synchronize access to a single DateFormat. This minimizes the number of formatter objects created, but different threads will have to contend for a lock before they can access the formatter. This is probably the worst alternative performance-wise; a lot of threads could end up spending time waiting, and the more threads you have the worse this will be.

You can create a new DateFormat object for each use. That will eliminate contention between threads, but if there is a lot of date formatting you could put pressure on the garbage collector with this approach, and that will hurt performance. But this can work well enough in many cases and is very simple.

A third alternative, making the DateFormat a threadlocal variable, is a lot more efficient. There is no contention between threads, and the formatter can be reused by a thread repeatedly, so it doesn't create nearly so much garbage. The downsides would be that it's the least straightforward approach, and any objects you put in a threadLocal may stick around longer than you want if you don't clear them out.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
3

Just create a new copy on each call until it's actually demonstrated to be a performance problem. The overhead of manually managing thread-locals is likely to swamp any advantage you get from caching them.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
3

You can

  1. Create a new DateFormat instance every time you need one.

  2. Use a synchronized block, as pointed by @Giovanni Botta.

  3. Use ThreadLocal:

    private static final ThreadLocal<DateFormat> THREADLOCAL_FORMAT =
        new ThreadLocal<DateFormat>() {
            @Override protected DateFormat initialValue() {
                return DateFormat.getDateTimeInstance();
            }
        };
    
    public static final String eventTypeToDateTimeString(long timestamp) {
        return THREADLOCAL_FORMAT.get().format(new Date(timestamp));
    }
    

Actually, using ThreadLocal might give you the best performance if you have a thread pool (meaning threads are reused), which most web containers do.

Reference

http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html

ericbn
  • 10,163
  • 3
  • 47
  • 55
2

Better, use: org.apache.commons.lang.time.FastDateFormat (is a fast and thread-safe version of SimpleDateFormat)

jmtalavera
  • 29
  • 1
0

The simplest solution would be:

synchronized public static final String eventTypeToDateTimeString(long timestamp) {
  return DATE_FORMAT.format(new Date(timestamp));
}

No need to use ThreadLocals since this is all in a static context.

Giovanni Botta
  • 9,626
  • 5
  • 51
  • 94
  • Every type of synchronization might affect performance. But you can always try different solutions and compare the impact on your system. There is no silver bullet. – Giovanni Botta Apr 06 '15 at 18:38
  • @assylias the OP asked about synchronization, so I just mentioned the simplest solution that reused the same instance by synchronizing on it. – Giovanni Botta Apr 06 '15 at 20:08
  • 1
    this attempt at an answer shows a profoundly ignorance understanding of thread safety and concurrency at the most basic and fundamental level. **Do Not do what this "answer" suggests**. –  Sep 05 '17 at 18:11