1

我在单个线程上运行一些 JUnit 测试,它们以不确定的方式失败。有人告诉我,优化的 JVM(Oracle Hotspot 64 位 17.1-b03)正在执行指令以提高速度。我很难相信 java 规范会允许这样做,但我找不到具体的参考。

Wikipedia 声明单个线程必须强制执行线程内如同串行,因此我不必担心执行顺序与我所写的不同。 http://en.wikipedia.org/wiki/Java_Memory_Model#The_memory_model

示例代码:

@Test
public void testPersistence() throws Exception
{
    // Setup
    final long preTestTimeStamp = System.currentTimeMillis();

    // Test
    persistenceMethod();

    // Validate
    final long postTestTimeStamp = System.currentTimeMillis();
    final long updateTimeStamp = -- load the timestamp from the database -- ;
    assertTrue("Updated time should be after the pretest time", updateTimeStamp >= preTestTimeStamp);
    assertTrue("Updated time should be before the posttest time", updateTimeStamp <= postTestTimeStamp);
}

void persistenceMethod()
{
    ...
    final long updateTime = System.currentTimeMillis();
    ...
    -- persist updateTime to the database --
    ...
}

运行此测试代码时,它具有完全不确定的行为,有时它通过,有时如果在第一个断言上失败,有时它在第二个断言上失败。这些值总是在一两毫秒之内,所以并不是说持久性完全失败了。添加一个 Thread.sleep(2); 在每个语句之间确实减少了测试失败的次数,但并没有完全消除失败。

这可能是 JVM 的错误,还是数据库(MsSql)更有可能对存储的数据进行某种舍入?

4

3 回答 3

1

JVM 无序执行语句的可能性非常小,我认为您几乎可以忽略它。如果 JVM 有这样的错误,除了你的这个程序之外,它还会出现在很多地方。

确实 currentTimeMillis 不能保证实际上精确到毫秒。但是时钟向后运行的可能性几乎与 JVM 无序执行语句的可能性一样遥远。

我编写了很多很多程序来测试我感兴趣的函数执行需要多长时间,方法是在它开始之前获取 currentTimeMillis,执行函数,在完成时获取 currentTimeMillis,然后减去以找到经过的时间。我从未有过这样的节目给我带来消极的时间。

我想到的一些可能性:

  1. 将时间戳保存到数据库或将其读回的代码中存在错误。您没有显示该代码,因此我们无法知道那里是否存在错误。

  2. 四舍五入。我手边没有 MySQL 实例,所以我不确定时间戳的精度是多少。如果它不如毫秒那么精确,那么这很容易解释你的问题。例如,假设它只精确到秒。你得到 pre time=01:00:00.1, update time=01:00:00.2, post time=01:00:00.4。但是更新时间被保存为 01:00:00 因为这是精度的限制,所以当你读回更新时间<重新时间。同样假设时间是 01:00:00.4、01:00:00.6、01:00:00.7。更新时间四舍五入到 01:00:01。所以更新时间>发布时间。

  3. 时区。默认时区是连接的属性。如果您在写时间时设置为东部时间,但当您读回时您是在太平洋时间,那么时间的顺序将不是您所期望的。

您为什么不打印所有三个时间戳的值,而不是只查看关系?我会将它们打印为整数和公历日期。哦,我会在保存之前打印更新时间,然后再读一遍。也许有些事情会变得明显。

例如,如果您看到读回的更新时间总是以一个或多个零结束,即使保存的时间有非零数字,这表明您的时间正在被截断或四舍五入。如果读回的时间与写入的时间相差 1 小时的精确倍数,则可能是时区问题。如果 post time 小于 pre time,则表明您的系统时钟存在严重问题,或者更有可能是程序错误混淆了时间。等等。

于 2011-07-11T21:24:38.930 回答
0

应该很容易确定 mySql(0r 你的持久性代码)是否正在做某事。让您的 persistenceMethod() 返回它持久的值并与您阅读的内容进行比较。他们肯定应该匹配。

我想知道是否是 currentTimeMillis() 的可信度有问题:

以毫秒为单位返回当前时间。请注意,虽然返回值的时间单位是毫秒,但值的粒度取决于底层操作系统,并且可能更大。例如,许多操作系统以几十毫秒为单位测量时间。

鉴于您正在进行 >= 测试,我不太清楚这可能会如何表现,但这让我想知道您到底在什么时候得到。

于 2011-07-11T20:46:19.123 回答
0

这真的很奇怪。如果这些语句可能具有影响后续语句的副作用,Java 肯定不会重新排列语句并以不同的顺序执行它们。

我认为这个错误的发生System.currentTimeMillis是因为没有你想象的那么精确。该方法的 API 文档说:

以毫秒为单位返回当前时间。请注意,虽然返回值的时间单位是毫秒,但值的粒度取决于底层操作系统,并且可能更大。例如,许多操作系统以几十毫秒为单位测量时间。

这听起来很奇怪,但在某些情况下,时间甚至可能会倒退,因此currentTimeMillis某一时刻返回的值可能低于前一时刻返回的值。看到这个问题:System.currentTimeMillis 总是返回一个值 >= 以前的调用吗?

于 2011-07-11T20:46:58.713 回答