cody
cody

Reputation: 6941

How to make a static Calendar thread safe

I'd like to use a Calendar for some static methods and use a static field:

private static Calendar calendar = Calendar.getInstance();

Now I read java.util.Calendar isn't thread safe. How can I make this thread safe (it should be static)?

Upvotes: 10

Views: 12997

Answers (6)

mrts
mrts

Reputation: 18963

Instead of trying to make the Calendar API thread-safe, use the thread-safe java.time API introduced in Java 8 instead. This API also provides a more fluent and intuitive interface for date and time manipulation.

Here's an example compatible with the legacy Date API of getting the start of next day with the java.time API:

import java.time.LocalDateTime;
import java.time.ZoneId;

public static Date getStartOfNextDay() {
    final ZonedDateTime startOfNextDay = ZonedDateTime.now()
            .plusDays(1)
            .withHour(0).withMinute(0).withSecond(0).withNano(0);
    return Date.from(startOfNextDay.toInstant());
}

And here's how it looks with the Calendar API:

import java.util.Calendar;

public static Date getStartOfNextDay() {
    final Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DAY_OF_MONTH, 1);
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    return calendar.getTime();
}

Upvotes: 2

Basil Bourque
Basil Bourque

Reputation: 339332

tl;dr

Calendar replaced by ZonedDateTime. Immutable, so thread-safe.

private static ZonedDateTime someMoment = ZonedDateTime.now();  // Capture the current moment as seen in the JVM’s current default time zone.

If the value is meant to be fixed, unchanging during the execution of your app, mark it final.

private static final ZonedDateTime someMoment = ZonedDateTime.now();  

If at runtime you might assign a different object, then for thread-safety use an AtomicReference.

private static final AtomicReference< ZonedDateTime > someMomentRef = new AtomicReference<>( ZonedDateTime.now() ) ;

Later updating with a fresh moment.

someMomentRef.set( ZonedDateTime.now() );

If you need to interoperate with old code not yet updated to java.time, convert.

ZonedDateTime someMoment = someMomentRef.get() ;
Calendar c = GregorianCalendar.from( someMoment ) ;  // `GregorianCalendar` is a concrete subclass of `Calendar`.

java.time.ZonedDateTime

The terribly flawed java.util.Calendar class has been supplanted by the modern java.time classes defined in JSR 310. Specifically replaced by java.time.ZonedDateTime.

A ZonedDateTime object represents a date with time-of-day as seen in a particular time zone.

Understand that a time zone is a named history of the changes to the offset used by the people of a particular region as decided by their politicians. An offset is merely a number of hours-minutes-seconds ahead or behind the temporal meridian of UTC.

To get the current moment, specify your desired/expected time zone.

ZoneId z = ZoneId.of( "Africa/Casablanca" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;

If you want the JVM’s current default time zone, use ZoneId.systemDefault().

You can store a ZonedDateTime object in a static reference. But be aware that such an object stores a frozen moment, is immutable, and does not adjust to later moments. If you want a later moment, call now method ago to generate a new fresh ZonedDateTime object.

private static final ZonedDateTime appLaunched = ZonedDateTime.now( ZoneId.of( "Asia/Tokyo" ) );

java.time.Instant

If you merely want to track a moment, a specific point on the timeline, without business rules explicitly aimed at time zones, use a moment as seen in UTC. For that, use java.time.Instant class.

private static final Instant instant = Instant.now() ;  // Always in UTC, an offset of zero.

Generating localized text

You can later apply a time zone for presentation to the user.

ZonedDateTime zdt = instant.atZone( ZoneId.systemDefault() ) ;
String output = 
    zdt
    .atZone( ZoneId.systemDefault() )  // Returns a `ZonedDateTime` object.
    .format( 
        DateTimeFormatter
        .ofLocalizedDateTime( FormatStyle.FULL )  // Returns a `DateTimeFormatter` object.
        .withLocale( Locale.getDefault() )
    ) ;  // Returns a `String` object.

Upvotes: 3

Andrew Lazarus
Andrew Lazarus

Reputation: 19340

Create a Calendar as a local variable in the method. If you need the same calendar across methods, you may be using statics where a (singleton or quasi-singleton) object would be more appropriate.

Upvotes: -1

Peter Lawrey
Peter Lawrey

Reputation: 533660

Calendar is thread safe provided you don't change it. The usage in your example is fine.

It is worth noting that Calendar is not an efficient class and you should only use it for complex operations (like finding the next month/year) IMHO: If you do use it for complex operations, use local variables only.

If all you want it a snapshot of the time a faster way is to use currentTimeMillis which does even create an object. You can make the field volatile if you want to make it thread safe.

private static long now = System.currentTimeMillis();

The usage is a bit suspect. Why would you get the current time and store it globally like this. It reminds me of the old joke.

- Do you have the time?
- Yes, I have it written down somewhere.

Upvotes: 3

Jon Skeet
Jon Skeet

Reputation: 1502016

You can't make something thread-safe if it isn't. In the case of Calendar, even reading data from it isn't thread-safe, as it can update internal data structures.

If at all possible, I'd suggest using Joda Time instead:

  • Most of the types are immutable
  • The immutable types are thread-safe
  • It's a generally much better API anyway

If you absolutely have to use a Calendar, you could create a locking object and put all the access through a lock. For example:

private static final Calendar calendar = Calendar.getInstance();
private static final Object calendarLock = new Object();

public static int getYear()
{
    synchronized(calendarLock)
    {
        return calendar.get(Calendar.YEAR);
    }
}

// Ditto for other methods

It's pretty nasty though. You could have just one synchronized method which created a clone of the original calendar each time it was needed, of course... it's possible that by calling computeFields or computeTime you could make subsequent read-operations thread-safe, of course, but personally I'd be loathe to try it.

Upvotes: 14

MJB
MJB

Reputation: 9399

You cannot. Yes, you could synchronize on it, but it still has mutable state fields. You'll have to create your own Calendar object.

If possible, use something lightweight, like a long measuring the times in milliseconds, and only convert to a Calendar when you NEED to.

Upvotes: 2

Related Questions