Java是否为单线程应用程序强制执行as-if-serial

时间:2011-07-11 20:32:58

标签: java optimization jvm single-threaded

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

维基百科指出,单个线程必须强制执行内部线程as-if-serial,因此我不必担心执行顺序与我编写的不同。 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)更有可能对存储的数据进行某种舍入?

3 个答案:

答案 0 :(得分:1)

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

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

我编写了许多程序来测试我感兴趣的函数需要多长时间才能执行,方法是在启动之前获取currentTimeMillis,执行函数,在完成时获取currentTimeMillis,然后减去查找已经过去的时间时间。我从来没有这样的计划给我一个负面的时间。

我遇到的一些可能性:

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

  2. 舍入。我没有方便的MySQL实例,所以我不确定时间戳的精度是多少。如果它不像毫秒那么精确,那么这很容易解释你的问题。例如,说它只对第二个准确。你得到pre time = 01:00:00.1,更新时间= 01:00:00.2,post time = 01:00:00.4。但更新时间保存为01:00:00,因为这是精度的限制,所以当你读回更新时间&lt;再来一次。同样地假设时间是01:00:00.4,01:00:00.6,01:00:00.7。更新时间四舍五入为01:00:01。那么更新时间&gt;发布时间。

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

  4. 为什么不打印所有三个时间戳的值,而不只是查看关系?我将它们打印为int's和Gregorian日期。哦,我会在保存之前打印更新时间,然后在阅读之后再次打印更新时间。也许某些事情会变得明显。

    例如,如果您发现读回的更新时间始终以一个或多个零结束,即使保存的时间具有非零数字,也表示您的时间被截断或舍入。如果回读的时间与写入1到1小时的精确倍数的时间不同,则可能是时区问题。如果发布时间小于预先时间,则表明系统时钟存在严重问题,或者更可能是程序错误导致时间混乱。等

答案 1 :(得分:0)

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

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

  

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

鉴于您正在进行&gt; =测试,我无法确定这可能会如何表现,但这让我想知道您到底的确切时间。

答案 2 :(得分:0)

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

我认为发生此错误是因为System.currentTimeMillis并不像您想象的那么精确。该方法的API文档说:

  

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

这听起来很奇怪,但在某些情况下,时间似乎甚至可能会倒退,因此currentTimeMillis在某一时刻返回的值可能会低于之前提前返回的值。请参阅此问题:Will System.currentTimeMillis always return a value >= previous calls?