Java Object.wait()超过超时时间

Java Object.wait()超过超时时间,java,Java,什么可以解释Object.wait(timeout)的持续时间超过了提供的超时值 long start = System.currentTimeMillis(); obj.wait(1000); long duration = System.currentTimeMillis() - start; // sometimes (very rarely) duration may exceed 1500 上下文:在一个非常复杂的软件的深处,有一段代码使这样的等待,并在持续时间过长时生成警告日志。在

什么可以解释
Object.wait(timeout)
的持续时间超过了提供的超时值

long start = System.currentTimeMillis();
obj.wait(1000);
long duration = System.currentTimeMillis() - start;
// sometimes (very rarely) duration may exceed 1500
上下文:在一个非常复杂的软件的深处,有一段代码使这样的
等待
,并在持续时间过长时生成警告日志。在高流量的生产环境中,一些日志报告了巨大的过度等待(如30秒)。因此,我试图重现它,了解可能发生的情况以及如何修复/改进它。

在“wait(timeout)”调用中花费的“用户时间”或“挂钟时间”通常是超时值加上线程重新计划执行和执行之前的时间

请参阅Javadoc以了解以下信息:

然后,线程T被[…]重新启用以进行线程调度。然后,它以通常的方式与其他线程竞争在对象上同步的权利

因此,不能保证“实时”操作,这更像是一种“最佳尝试”,取决于当前的系统负载,也可能取决于应用程序中的其他锁定依赖项。因此,如果系统负载很重,或者您的应用程序处理许多线程,那么等待时间可能比超时时间长得多

PS
nathan hughes在对您的问题的评论中提到的这句话可能是Javadoc中“wait”方法的关键语句:
指定的实时时间已经过去了,或多或少

PPS
根据您的问题,使用附加上下文信息进行编辑(“非常复杂的软件”、“高流量”、“巨大的过度等待”):您必须找到
obj
对象作为锁的所有用法,并确定这些用法如何相互作用

这会变得非常复杂。在这里,尝试勾勒出一个可能出错的“简单”场景,其中只有两个普通线程,例如:

// thread 1
synchronized (obj) {
    // wait 1000ms
    obj.wait(1000);
}
// check for overwait

// thread 2, after, let's say 500 ms
synchronized (obj) {
    obj.notify();
}
简单场景,一切正常,执行顺序大致如下:

  • 0ms:T1获得“obj”上的锁
  • 0ms:T1将自身注册为等待“obj”,并从线程调度中排除当从线程调度中排除时,“obj”上的锁再次被释放(!)
  • 500ms:T2获取“obj”上的锁,通知一个等待通知的线程(根据线程调度设置选择线程),并释放“obj”上的锁
  • 500ms+X:T1已重新启用线程调度,它将等待重新获得对“obj”(!)的锁定,然后完成阻塞并释放对“obj”的锁定
  • 这些只是2个简单线程和
    同步的
    块。让我们用写得不好的代码使这个问题变得更复杂。如果第二个线程是这样的:

    // bad variant of thread 2, after, let's say 500 ms
    synchronized (obj) {
        obj.notify();
    
        // do complex operation, taking more than few ms,
        // maybe a heavy SQL query/update...
    }
    
    在这种情况下,即使T1已收到通知(或可能超时),它也必须等待,直到再次获得“obj”上的锁。,只要复杂操作运行(上一列表中的步骤3),该锁仍由T2保持!这可能真的需要。。。秒或更多

    更复杂的是:我们返回到最初的简单线程T1和T2,但添加了第三个线程:

    // thread 3, after, let's say also 500 ms
    synchronized (obj) {
        // do complex operation, taking more than few ms,
        // maybe a heavy SQL query/update...
    }
    
    执行顺序大致可以是:

  • 0ms:T1获得“obj”上的锁
  • 0ms:T1将自身注册为等待“obj”,并从线程调度中排除当从线程调度中排除时,“obj”上的锁再次被释放(!)
  • 500ms:T2获取“obj”上的锁,通知一个等待通知的线程(根据线程调度设置选择线程),并释放“obj”上的锁
  • 500ms+X:T2已重新启用线程调度,但未获得“obj”上的锁,因为
  • 500ms+X:T3在T1之前由线程调度程序调度,它获得“obj”(!)上的锁,并开始执行其复杂的操作。T1除了等待什么都做不了
  • 500毫秒+多个:T3*释放“obj”上的锁
  • 500ms+MANY:T1获取“obj”(!)上的锁,然后退出其同步块并释放“obj”上的锁
  • 这只是在“高流量”的“非常复杂的软件”中可能发生的事情的表面。添加更多的线程,可能编码不好(例如,在“同步”块中执行太多任务),高流量,您可能很容易得到您提到的过度等待

    选项
    如何解决这个问题。。。根据软件的用途和复杂性,没有简单的计划。根据现有的信息,还不能说更多

    也许用笔和纸重新分析代码就足够了,也许分析它可以帮助您找到锁,也许您可以通过JMX或线程转储(通过signal、jconsole、jcmd、jvisualvm)或通过Java任务控制和Java飞行记录来获得关于当前锁的所需信息(我想功能是从JDK 7u40开始提供的)

    您在评论中询问了
    Thread.sleep(timeout)
    是否会有帮助:没有更多信息就不能说。也许会有帮助。或者重入锁或其他锁定选项(请参阅包)更合适。这取决于您的代码、您的用例和您使用的Java版本

    如果GC不是问题(见下文),并且分析代码,它看起来“不错”,并且你认为高流量是原因,你也可以考虑启用偏压锁定或自旋锁定。请参阅更多细节(文章包含java 8 JVM选项的链接)。 垃圾收集


    顺便说一句,“高流量”应该让我早些时候问:垃圾收集,你监控过吗?如果没有正确配置/调优,GC也可能会导致非常严重的暂停!(我本周遇到过这样的情况,完整GC需要15-30秒…)

    当线程在超时过期后实际唤醒或睡眠唤醒时,情况并不准确
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    ThreadInfo[] ti = bean.getThreadInfo(bean.getAllThreadIds(), true, true);
    
    -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=8989
    
    -Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8888