一个更好的运行代码一段时间的方法

9
我需要运行一些代码一段预设长度的时间,当时间结束时,它需要停止。目前,我正在使用TimerTask允许代码执行一定的时间,但这会导致代码创建无数线程,并且效率低下。有更好的替代方案吗?
目前的代码:
// Calculate the new lines to draw 
            Timer timer3 = new Timer();
            timer3.schedule(new TimerTask(){
                public void run(){
                    ArrayList<String> Coords = new ArrayList<String>();
                    int x = Float.valueOf(lastFour[0]).intValue();
                    int y = Float.valueOf(lastFour[1]).intValue();
                    int x1 = Float.valueOf(lastFour[2]).intValue();
                    int y1 = Float.valueOf(lastFour[3]).intValue();
                    //Could be the wrong way round (x1,y1,x,y)?
                    Coords = CoordFiller.coordFillCalc(x, y, x1, y1);
                    String newCoOrds = "";
                    for (int j = 0; j < Coords.size(); j++)
                    {
                        newCoOrds += Coords.get(j) + " ";
                    }
                    newCoOrds.trim();
                    ClientStorage.storeAmmendedMotion(newCoOrds);

                }

            }
            ,time);
4个回答

17
如果您使用的是Java5或更高版本,请考虑使用ScheduledThreadPoolExecutorFuture。前者可以安排任务在指定延迟后运行,或者在指定间隔后运行,因此它取代了Timer的角色,但更加可靠。

Timer 设施管理延迟执行(“在100毫秒后运行此任务”)和周期性执行(“每10毫秒运行一次此任务”)的任务。然而,Timer 有一些缺点,应该将 ScheduledThreadPoolExecutor 视为其替代品。[...]

Timer 仅创建一个线程来执行定时器任务。如果定时器任务运行时间过长,则其他 TimerTask 的定时精度可能会受到影响。如果将一个重复的 TimerTask 定于每10毫秒运行一次,并且另一个 TimerTask 运行需要40毫秒,则在长时间运行的任务完成后,重复的任务要么(取决于它是否以固定速率或固定延迟定时)在快速连续的四次调用之后被调用,要么完全“错过”四次调用。通过让您提供多个线程来执行延迟和周期性任务,计划线程池可以解决这个限制。

Timer 的另一个问题是,如果 TimerTask 抛出未经检查的异常,则其表现不佳。 Timer 线程不捕获异常,因此从 TimerTask 抛出的未经检查的异常会终止计时器线程。在这种情况下,Timer 也不会恢复线程;相反,它错误地假定整个 Timer 已被取消。在这种情况下,已经预定但尚未执行的 TimerTask 永远不会运行,并且无法安排新任务。

摘自《Java并发编程实战》第6.2.5节。

Future 可以被限制在指定时间内运行(如果无法在规定时间内完成,将抛出TimeoutException异常)。

更新

如果您不喜欢上述方法,您可以让任务自己测量执行时间,如下所示:

int totalTime = 50000; // in nanoseconds
long startTime = System.getNanoTime();
boolean toFinish = false;

while (!toFinish) 
{
    System.out.println("Task!");
    ...
    toFinish = (System.getNanoTime() - startTime >= totalTime);
}

1
谢谢,这看起来很有帮助。我需要的是让代码在一段时间内执行。ScheduledThreadPoolExecutor似乎只能使用延迟和等待,而不能执行一段时间的任务,类似于等待执行完成的Future。 - mhollander38
@mhollander38,我理解的是,如果你调用Future.get(1, TimeUnit.SECOND),在一秒钟后它将抛出一个TimeoutException并且关联任务的执行将会被终止。不过我还没有验证过。 - Péter Török
@mhollander38,我添加了另一种解决方案,改进了您(现已删除的)转帖中的代码。 - Péter Török
现在我已经实施了它,而且它真的很有效。非常感谢你,Péter。 - mhollander38
@Péter Török 在调用Future.get(1, TimeUnit.SECOND)时出现的超时对底层任务没有任何影响。 "不耐烦的调用者"会收到一个TimeoutException,因此它可以做其他事情,但仅限于此。 - maaartinus

4
目前我正在使用TimerTask来允许代码执行一段时间[...]。
计时器任务永远不会停止当前正在运行的任务。事实上,它唯一的目的就是一遍又一遍地重新启动任务。
没有简单的方法可以解决这个问题,除非与正在执行的任务紧密合作。最好的方法是让任务监视自己的执行,并确保在时间到期时返回(终止)。

尽管Thread.stop()被提及,但是应该在巨大的警告标签<blink>周围强调它绝不是任何问题的正确解决方法,特别是对于那些需要询问计时器执行的人。如果一个任务需要运行一定的时间长度,你需要告诉任务,并像@aioobe建议的那样让它自我监控。 - andersoj

0

需要注意的是,通常只需要创建一个Timer()。从代码片段中可以猜测您正在创建多个Timer()对象。

计划方法中的时间是运行时间,而不是运行时长。

在for循环之前考虑设置开始时间,并在超过时间限制时在for循环中加入break。

long startedAt = System.currentTimeMillis();
long finishedCorrectly = true;
for (int j = 0; j < Coords.size(); j++) {
  newCoOrds += Coords.get(j) + " ";
  if ((System.currentTimeMillis() - startedAt) > MAX_TIME_TO_RUN) {
    finishedCorrectly = false;
    break;
  }
}

0

如果你的意思是程序必须退出,解决方案是为处理创建一个线程并将其标记为守护进程,启动它并在主线程中休眠所需时间,然后从main()方法中简单地返回。

如果你的意思只是停止处理,请忽略上述内容。


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