我在一个线程上运行一些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)更有可能对存储的数据进行某种舍入?
答案 0 :(得分:1)
JVM执行语句乱序的可能性非常小,我认为你几乎可以忽略它。如果JVM有这样的bug,除了你的这个程序之外,它会出现在很多地方。
确实currentTimeMillis实际上不能保证精确到毫秒。但是时钟向后运行的可能性几乎与JVM无序执行语句的可能性一样遥远。
我编写了许多程序来测试我感兴趣的函数需要多长时间才能执行,方法是在启动之前获取currentTimeMillis,执行函数,在完成时获取currentTimeMillis,然后减去查找已经过去的时间时间。我从来没有这样的计划给我一个负面的时间。
我遇到的一些可能性:
您的代码中存在错误,无法将时间戳保存到数据库或将其读回。你没有显示那些代码,所以我们无法知道那里是否有错误。
舍入。我没有方便的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;发布时间。
时区。默认时区是连接的属性。如果你把你设定的时间写成东部时间,但当你读回来的时候你是在太平洋时间,那么时间的顺序将不是你所期望的。
为什么不打印所有三个时间戳的值,而不只是查看关系?我将它们打印为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?