Java计时器Timer和计时器任务TimerTask多线程问题

9

我将使用Timer和TimerTask来进行聊天应用程序的长轮询以获取新消息。

我想要探讨两种“稍微”不同的可能性:

1: 将Timer声明为局部变量

public List<MessageBean> getLastMessages(...) {
    [...]
    Timer timer = new Timer(true); //**Timer declared as local variable**
    while (someCondiction) {
        MessagesTimerTask returnMessagesTask = new MessagesTimerTask([...]);
        timer.schedule(returnMessagesTask, 6000);
        synchronized (listMessageBean) {
            listMessageBean.wait();
            //notify is called in the MessagesTimerTask which extends TimerTask
        }
    }
}
问题:每次调用该方法时,我都可以看到会创建一个新的线程,[Timer-1]、[Timer-2]等等。即使客户端getLastMessages(..)结束运行并返回值后,在Eclipse Debug窗口中,这些线程似乎仍在运行。如果这些计时器确实使用了线程,并且经过几个事务之后,服务器最终将消耗所有的机器资源,则可能会导致巨大问题。

2:计时器声明为局部字段

private final Timer timer = new Timer(true); //**Timer declared as local field**

public List<MessageBean> getLastMessages(...) {
    [...]
    while (someCondiction) {
        MessagesTimerTask returnMessagesTask = new MessagesTimerTask([...]);
        timer.schedule(returnMessagesTask, 6000);
        synchronized (listMessageBean) {
            listMessageBean.wait();
            //notify is called in the MessagesTimerTask which extends TimerTask
        }
    }
}
问题:每次调用方法时,都使用相同的线程[Thread-1],但我不确定如果我进行两个连续的调用,后一个会取消/覆盖前一个(该类由Spring @Autowired)?

有什么建议吗? 谢谢。


从性能角度来看,选项2)将非常有效。 - Bhavik Ambani
@BhavikAmbani 好的,但如果我调用服务两次,计时器对象将在两次调用中是相同的对象.. 那么第二次调用会重新安排另一个任务,从而取消之前的任务吗? - Majid Laissi
有人在同一分钟内把我所有的问题都踩了...!! - Majid Laissi
1
@jidma:看起来有人连续给你投了反对票。如果你愿意,可以在 meta.stackoverflow 上报告此事。 - Tudor
1个回答

8
这里是schedule方法的源代码
190       public void schedule(TimerTask task, long delay) {
191           if (delay < 0)
192               throw new IllegalArgumentException("Negative delay.");
193           sched(task, System.currentTimeMillis()+delay, 0);
194       }

还有 sched 方法:

386       private void sched(TimerTask task, long time, long period) {
387           if (time < 0)
388               throw new IllegalArgumentException("Illegal execution time.");
389   
390           // Constrain value of period sufficiently to prevent numeric
391           // overflow while still being effectively infinitely large.
392           if (Math.abs(period) > (Long.MAX_VALUE >> 1))
393               period >>= 1;
394   
395           synchronized(queue) {
396               if (!thread.newTasksMayBeScheduled)
397                   throw new IllegalStateException("Timer already cancelled.");
398   
399               synchronized(task.lock) {
400                   if (task.state != TimerTask.VIRGIN)
401                       throw new IllegalStateException(
402                           "Task already scheduled or cancelled");
403                   task.nextExecutionTime = time;
404                   task.period = period;
405                   task.state = TimerTask.SCHEDULED;
406               }
407   
408               queue.add(task);
409               if (queue.getMin() == task)
410                   queue.notify();
411           }
412       }

从这里可以清楚地看到,队列被用来内部存储任务,这意味着后续的任务不会覆盖之前的任务。如果您还检查文件中的mainLoop方法,您可以看到它按照计划时间一个接一个地从队列中取出任务并执行。
因此,在同一个Timer对象上安排多个任务不应该是问题。
另外,考虑将Timer替换为Java 1.5以来可用的ScheduledThreadPoolExecutor

谢谢你的侧记,使用ScheduledThreadPoolExecutor替换计时器还解决了一些其他线程相关的问题。 - Mark Schäfer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接