我想使用一个日历来实现一些静态方法并使用一个静态字段:
private static Calendar calendar = Calendar.getInstance();
我了解到java.util.Calendar不是线程安全的。如何使它线程安全(它应该是静态的)?
我想使用一个日历来实现一些静态方法并使用一个静态字段:
private static Calendar calendar = Calendar.getInstance();
我了解到java.util.Calendar不是线程安全的。如何使它线程安全(它应该是静态的)?
Calendar
的情况下,即使仅从中读取数据,也不是线程安全的,因为它可能会更新内部数据结构。Calendar
,你可以创建一个锁对象,并通过锁来访问所有的操作。例如: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
不过这种方式相当恶劣。当然,你可以只编写一个同步方法,每次需要它时都创建原始日历的克隆副本......当然,通过调用computeFields
或computeTime
,你可以使后续的读操作线程安全,但就我个人而言,我不愿尝试。
areFieldsSet
变成可见的true
,而计算出的字段值本身还没有变得可见,将会发生什么。这可能导致一个线程读取一些旧字段值和一些新字段值,从而导致奇怪的结果。我不喜欢尝试推断实现细节来决定是否可能发生这种情况。 - Jon Skeetlong
。 - bestsss日历是线程安全的,只要你不改变它。在你的例子中使用它是可以的。
值得注意的是,日历不是一个高效的类,你应该只在复杂操作(比如查找下个月/年)时使用它。如果你确实要使用它进行复杂操作,请仅使用局部变量。
如果你只想要时间的快照,更快的方法是使用currentTimeMillis,它甚至不会创建对象。如果你想使它线程安全,可以将字段设置为volatile。
private static long now = System.currentTimeMillis();
这种用法有些可疑。为什么要获取当前时间并在全局范围内存储它呢?这让我想起了一个老笑话:
- 你知道现在几点吗?
- 是的,我在某个地方写下了它。
String
设计为线程安全,因此其哈希缓存也是线程安全的:该值从“未缓存”原子性地更改为“已缓存的值”。与此相比,Calendar
中有一个布尔字段确定是否设置了字段,并且字段本身是分离的。什么保证对该数据集的所有更改是以原子方式进行的,或者以期望的顺序进行的? - Jon SkeetCalendar
被ZonedDateTime
取代。 不可变性,因此线程安全。
private static ZonedDateTime someMoment = ZonedDateTime.now(); // Capture the current moment as seen in the JVM’s current default time zone.
final
。private static final ZonedDateTime someMoment = ZonedDateTime.now();
AtomicReference
。private static final AtomicReference< ZonedDateTime > someMomentRef = new AtomicReference<>( ZonedDateTime.now() ) ;
someMomentRef.set( ZonedDateTime.now() );
ZonedDateTime someMoment = someMomentRef.get() ;
Calendar c = GregorianCalendar.from( someMoment ) ; // `GregorianCalendar` is a concrete subclass of `Calendar`.
java.time.ZonedDateTime
可怕有缺陷的 java.util.Calendar
类已被定义在JSR 310中的现代java.time类所取代。具体被java.time.ZonedDateTime
替代。
ZonedDateTime
对象表示在特定时区中看到的带时间的日期。
了解时区是一个由他们的政治家决定的特定地区居民使用的偏移更改的历史名称。偏移仅仅是比UTC的时间子午线提前或推后的小时-分钟-秒数。
要获取当前时刻,请指定您所期望的时区。
ZoneId z = ZoneId.of( "Africa/Casablanca" ) ;
ZonedDateTime zdt = ZonedDateTime.now( z ) ;
如果您想获取JVM的当前默认时区,请使用ZoneId.systemDefault()
。
您可以将ZonedDateTime
对象存储在static
引用中。但请注意,这样的对象存储了一个固定的时刻,是不可变的,并且不会调整到以后的时刻。如果您想要以后的时刻,请调用now
方法生成一个新的ZonedDateTime
对象。
private static final ZonedDateTime appLaunched = ZonedDateTime.now( ZoneId.of( "Asia/Tokyo" ) );
java.time.Instant
如果你只是想追踪一个瞬间,时间线上的特定点,而没有明确针对时区的业务规则,请使用以UTC为基准的瞬间(moment)。为此,使用java.time.Instant
类。
private static final Instant instant = Instant.now() ; // Always in UTC, an offset of zero.
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.
不要试图使Calendar
API线程安全,而是使用Java 8中引入的线程安全的java.time
API。该API还提供了更流畅和直观的日期和时间操作界面。
以下是一个与传统Date
API兼容的示例,使用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());
}
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();
}
LocalDateTime
有什么价值。替代java.util.Calendar
的是java.time.ZonedDateTime
。 - Basil BourqueCalendar
。如果您需要在多个方法中使用相同的日历,则可以使用静态变量,其中单例或准单例对象更合适。