【问题标题】:Java Timer fires out of bandJava Timer 带外触发
【发布时间】:2021-03-19 16:42:07
【问题描述】:

我创建了一个 Java 类,它每分钟检查是否有心跳信号通过某个队列到达。虽然到目前为止效果很好,但生产中报告了一个奇怪的案例,其中每毫秒或什至在一毫秒内两次在日志中都有一个条目。这持续了大约 40 分钟,直到管理员检测到问题并重新启动应用程序。之后,心跳队列每分钟根据日志再次检查一次,这是预期的行为。

有问题的日志条目看起来(简化)如下:

16-03-2021 01:20:20.098 HEARTBEAT_NOT_OK. No initial heartbeat from server after application startup at 01.01.1970-00:01:09.444 for 18702 day(s) 01:19:10 (days hh:mm:ss). Heartbeat interval 60000 ms.
16-03-2021 01:20:20.099 HEARTBEAT_NOT_OK. No initial heartbeat from server after application startup at 01.01.1970-00:01:09.444 for 18702 day(s) 01:19:10 (days hh:mm:ss). Heartbeat interval 60000 ms.
16-03-2021 01:20:20.099 HEARTBEAT_NOT_OK. No initial heartbeat from server after application startup at 01.01.1970-00:01:09.444 for 18702 day(s) 01:19:10 (days hh:mm:ss). Heartbeat interval 60000 ms.
16-03-2021 01:20:20.100 HEARTBEAT_NOT_OK. No initial heartbeat from server after application startup at 01.01.1970-00:01:09.444 for 18702 day(s) 01:19:10 (days hh:mm:ss). Heartbeat interval 60000 ms.

日志文本总是相同的,我找不到解释。这是应该每分钟检查一次心跳队列的那个 Java 类的简化代码:

@Slf4j
@Component(service = ServiceProvider.class)
public class HeartbeatReceiver implements ServiceProvider {

private static DateFormat DateFormatter = new SimpleDateFormat("dd.MM.yyyy-HH:mm:ss.SSS");
private static long DefaultHeartbeatPeriodInMillis = 60_000;

private Timer timer;
private ReceivedHeartbeat lastHeartbeat = null;
private long heartbeatPeriod = DefaultHeartbeatPeriodInMillis;
private Object heartbeatPeriodLock = new Object();
private volatile long watchdogStartTime;
private Object heartbeatAliveLock = new Object();


@Activate
public void activate() {
    startWatchdog();
}

private void startWatchdog() {
    watchdogStartTime = System.currentTimeMillis();
    startWatchdogTimer();
}

private void startWatchdogTimer() {
    timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            checkHeartbeat();
        }
    }, delayTillFullMinute(), getHeartbeatPeriod());
}

private long delayTillFullMinute() {
    return 0; // omitted for brevity
}

public long getHeartbeatPeriod() {
    synchronized (heartbeatPeriodLock) {
        return heartbeatPeriod;
    }
}

private void checkHeartbeat() {
    synchronized (heartbeatAliveLock) {
        if (lastHeartbeat == null) { // just started up
            long duration = System.currentTimeMillis() - watchdogStartTime;
            if(duration >= heartbeatPeriod) {
                logNoInitialHeartbeatReceivedAfterStartup(duration);
            }
            // omitted for brevity
            return;
        }

        // omitted for brevity
    }
}

private void logNoInitialHeartbeatReceivedAfterStartup(long duration) {
    log.error("HEARTBEAT_NOT_OK. No initial heartbeat from server after application startup at {} for {}. Heartbeat interval {} ms.",
        DateFormatter.format(watchdogStartTime), parseTime(duration), heartbeatPeriod);
}

public static String parseTime(long time) {
    return "foo"; // omitted for brevity
}

// other code omitted for brevity

}

我的猜测是 OSGi @Activate 注释会导致一些类初始化问题,因为没有正确初始化 var heartbeatPeriod。但随后日志输出应显示日期 01.01.1970-00:00:00.000 而不是 01.01.1970-00:01:09.444。这个奇怪的日期 01.01.1970-00:01:09.444 也让我感到困惑。

【问题讨论】:

    标签: java timer osgi timertask


    【解决方案1】:

    您正在创建一个计时器任务,但您从未在停用时取消它。 (也不关闭Timer本身,会一直占用一个线程。)

    那么你确定这不是一个正在运行的旧计时器吗?

    【讨论】:

    • 谢谢彼得,这是一个很好的观点。我们的应用程序通过杀死它运行的操作系统进程来关闭。这不是应该的。但结果是所有的 deactivate 方法都不会被调用。不过,您提出的观点让我对错误可能是什么有了新的想法:-)。
    • OSGi 中的一个讨厌的问题是“jojoing”。在启动时,组件会被创建/删除,因为它们以默认配置启动,然后由 Config Admin 配置,或者它们由 Config Admin 启动,然后 Configurator 提供新配置。无论如何,组件应该始终正确清理。
    • 如果这与问题有关,请报告。
    • 不知道这个“jojoing”。当您从 OSGi 专家那里得到答案时,您很幸运 :-)。我会试试这个。因为它只发生过一次,而且从未发生过,因为它可能很难重新创建,然后确保它已修复。但我会根据您的观点进行修复,并增加日志以查看是否发生了“jojoing”。
    【解决方案2】:

    只是一个疯狂的猜测,但你正在同步

    private Object heartbeatAliveLock = new Object();
    

    为什么不同步

    private HeartbeatReceiver heartbeatAliveLock  = null;
    .
    .
    .
    public HeartbeatReceiver()
    {
       super();
    
       heartbeatAliveLock = this;
    }
    

    【讨论】:

    • 你好大家伙。通过 this 指针进行同步意味着代码中的任何其他同步块都将关闭,以供其他线程进入。通过对特定的锁定对象进行同步,您可以获得更细粒度的控制,防止线程处于此同步块中,从而锁定所有其他想要进入某个同步块的线程。
    【解决方案3】:

    01.01.1970 是纪元时间。即0L。如果您真的对经过时间感兴趣,那么我建议您改用System.nanoTime,这对于经过时间计算更安全。 System.currentTimeMillis 用于了解系统时间,可以在运行时更改。

    【讨论】:

    • 感谢您的提示。我会利用这个。但是,管理员报告说他没有更改系统时间。所以计时器如何用尽的问题仍然存在。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-07
    • 1970-01-01
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多