【问题标题】:How to track task execution statistics using an ExecutorService?如何使用 ExecutorService 跟踪任务执行统计信息?
【发布时间】:2010-10-29 17:04:36
【问题描述】:

我正在使用 ExecutorService 启动任务,分派需要按任务特定标准分组的任务:

Task[type=a]
Task[type=b]
Task[type=a]
...

我想定期输出每个任务花费的平均时间长度(按type 分组)以及统计信息,例如平均值/中位数和标准差。

当然,这需要非常快,并且理想情况下不应导致各个线程在报告统计信息时同步。这样做的好架构是什么?

【问题讨论】:

  • 我应该注意;我知道在哪里我将从中调用这些方法,我想知道我应该使用什么来积累数据。
  • 每种任务类型都有自己的 Runnable 吗?
  • 是的,确实如此。当然是非常基本的东西,但是在调度任务之前会在任务中存储一些信息。

标签: java concurrency statistics monitoring


【解决方案1】:

我同意@Robert Munteanu 的观点。线程池中的beforeExecute 确实毫无价值,尽管文档说它可以用于统计。但实际上,我们无法在我们的情况下检查 runnable 的身份。

我认为包装器可以到达这个。

public interface ICallableHook<V> {
    void beforeExecute(Thread t, Callable<V> callable);
    void afterExecute(Callable<V> callable, V result, Throwable e);
}


private class CallableWrapper<V> implements Callable<V> {
        private ICallableHook hooker;
        private Callable<V> callable;

        CallableWrapper(Callable callable, ICallableHook hooker) {
            this.callable = callable;
            this.hooker = hooker;
        }




    @Override
    public V call() throws Exception {
        if (hooker != null) {
            hooker.beforeExecute(Thread.currentThread(), callable);
        }

        V result = null;
        Exception exception = null;
        try {
            result = callable.call();
        } catch (Exception e) {
            exception = e;
            throw e;
        } finally {
            if (hooker != null) {
                hooker.afterExecute(callable, result, exception);
            }
        }
        return result;
    }
}

这样的用法,

  for (Callable<XXX> callable : callableList) {
        CallableWrapper<XXX> callableWrapper = new CallableWrapper<>(callable, hooker);
        Future task = completionService.submit(callableWrapper);

    }

【讨论】:

    【解决方案2】:

    另一种方法是使用包装器/装饰器模式。

    public class Job implements Runnable {
    private Runnable _task;
    private Statistics _statistics;
    
    public Job(Runnable task, Statistics statistics) {
        this._task = task;
    }
    
    public void run() {
        long s = System.currentTimeMillis();
        _task.run();
        long e = System.currentTimeMillis();
    
        long executionTime = e - s;
        _statistics.updateStatistics(executionTime);
    }
    }
    

    【讨论】:

      【解决方案3】:

      我相信其他两个答案是正确的,但可能有点过于复杂(尽管我的答案虽然简单,但可能不如他们的答案那么好。

      为什么不只使用原子变量来跟踪您的统计数据?例如运行的任务数,总执行时间(除以总数,你得到平均执行时间)。将这些变量传递到每个任务的 Runnable 中。除非您的任务非常短暂,否则我认为锁定 Atomic 变量的开销不会影响您。

      【讨论】:

        【解决方案4】:

        ThreadPoolExecutor 提供了您可以覆盖的 beforeExecuteafterExecute 方法。您可以使用它们将您的统计信息记录在单个(您的 ExecutorService 的成员变量)ConcurrentHashMap 中,键入您的任务的一些唯一标识符,并存储类型、开始时间和结束时间。

        当您准备好查看 ConcurrentHashMap 时,计算统计数据。

        【讨论】:

          【解决方案5】:

          子类Thread Pool Executor 并跟踪执行事件:

          值得注意的是,方法是由执行任务的工作线程调用的,因此需要确保执行跟踪代码的线程安全。

          此外,您将收到的 Runnables 很可能不是您的 Runnables,而是包装在 FutureTasks 中。

          【讨论】:

          • 您的最后一点非常重要。对于提交给 ThreadPoolExecutor 的 Callables,我有同样的问题。不幸的是,进入 beforeExecute 的 Runnable 是一个包装了我的 Callable 的 FutureTask(看起来提交的 Runnable 也是如此)。因此,没有简单的方法可以访问您的原始 Runnable/Callable。令人失望... :( 看起来我也会重写 submit() 以保持 Map 从 Future 到 Callable。我希望它不会让事情变得太慢。
          • 当然,尽管文档说它可以用于统计,但它确实没有任何价值。但实际上,我们无法检查在我们的情况下哪个可运行。
          猜你喜欢
          • 2022-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-06-19
          • 2014-10-24
          相关资源
          最近更新 更多