【问题标题】:Bound Thread running time绑定线程运行时间
【发布时间】:2012-05-26 22:40:34
【问题描述】:

我正在尝试查找有关如何限制使用ThreadPoolExecutor 创建的任务的运行时间的更多信息。

我想创建一个自毁的,例如当时间过去(例如 1m)时,线程将自动终止并返回一个空值。这里的关键点是等待线程完成不应该阻塞主线程(在我们的例子中是 UI 线程)。

我知道我可以使用get 方法,但是它会阻止我的应用程序。

我正在考虑运行一个额外的内部线程,它会休眠 1m,然后会在主线程上调用中断。

我附上了一个示例代码,它看起来是个好主意,但我需要另一双眼睛告诉我它是否有意义。

public abstract class AbstractTask<T> implements Callable<T> {
private final class StopRunningThread implements Runnable {
    /**
     * Holds the main thread to interrupt. Cannot be null.
     */
    private final Thread mMain;

    public StopRunningThread(final Thread main) {
        mMain = main;

    }
    @Override
    public void run() {
        try {
            Thread.sleep(60 * 1000);
            // Stop it.
            mMain.interrupt();
        } catch (final InterruptedException exception) {
            // Ignore.
        }
    }
}

call() 通过线程池调用

public T call() {
    try {
        // Before running any task initialize the result so that the user
        // won't
        // think he/she has something.
        mResult = null;
        mException = null;
        // Stop running thread.
        mStopThread = new Thread(new StopRunningThread(
                Thread.currentThread()));
        mStopThread.start();

        mResult = execute(); <-- A subclass implements this one
    } catch (final Exception e) {
        // An error occurred, ignore any result.
        mResult = null;
        mException = e;
        // Log it.
        Ln.e(e);
    }
    // In case it's out of memory do a special catch.
    catch (final OutOfMemoryError e) {
        // An error occurred, ignore any result.
        mResult = null;
        mException = new UncheckedException(e);
        // Log it.
        Ln.e(e);
    } finally {
        // Stop counting.
        mStopThread.interrupt();
    }

    return mResult;
}

有几点我很害怕:

  • 如果 execute() 发生异常,然后我的外部线程立即中断,我将永远无法捕获该异常。
  • 内存/CPU消耗,我正在使用线程池来避免创建新线程。

您认为实现相同功能的更好主意吗?

【问题讨论】:

    标签: java multithreading threadpool


    【解决方案1】:

    这样做会有些麻烦。首先,您需要扩展 ThreadPoolExecutor 类。您需要覆盖“beforeExecute”和“afterExecute”方法。他们将跟踪线程开始时间,并在之后进行清理。然后你需要一个收割者来定期检查哪些线程需要清理。

    本示例使用 Map 来记录每个线程的启动时间。 beforeExecute 方法填充它,而 afterExecute 方法清除它。有一个 TimerTask 定期执行并查看所有当前条目(即所有正在运行的线程),并对所有超过给定时间限制的条目调用 Thread.interrupt()。

    请注意,我提供了两个额外的构造函数参数:maxExecutionTime 和 reaperInterval 来控制任务的执行时间,以及检查要终止的任务的频率。为了简洁起见,我在这里省略了一些构造函数。

    请记住,您提交的任务必须表现良好并允许自己被杀死。这意味着您必须:

    1. 定期检查 Thread.currentThread().isInterrupted() 在执行期间。
    2. 尽量避免任何不声明的阻塞操作 它的 throws 子句中的 InterruptedException。一个典型的例子 将是 InputStream/OutputStream 用法,您将使用 NIO 而是频道。如果必须使用这些方法,请在此类操作返回后立即检查中断标志。

    .

    public class TimedThreadPoolExecutor extends ThreadPoolExecutor {
        private Map<Thread, Long> threads = new HashMap<Thread, Long>();
        private Timer timer;
    
        public TimedThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
                long maxExecutionTime,
                long reaperInterval) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
            startReaper(maxExecutionTime, reaperInterval);
        }
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            threads.remove(Thread.currentThread());
            System.out.println("after: " + Thread.currentThread().getName());
            super.afterExecute(r, t);
        }
    
        @Override
        protected void beforeExecute(Thread t, Runnable r) {
            super.beforeExecute(t, r);
            System.out.println("before: " + t.getName());
            threads.put(t, System.currentTimeMillis());
        }
    
    @Override
    protected void terminated() {
        if (timer != null) {
            timer.cancel();
        }
        super.terminated();
    }
    
        private void startReaper(final long maxExecutionTime, long reaperInterval) {
            timer = new Timer();
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    // make a copy to avoid concurrency issues.
                    List<Map.Entry<Thread, Long>> entries = 
                            new ArrayList<Map.Entry<Thread, Long>>(threads.entrySet());
                    for (Map.Entry<Thread, Long> entry : entries) {
                        Thread thread = entry.getKey();
                        long start = entry.getValue();
                        if (System.currentTimeMillis() - start > maxExecutionTime) {
                            System.out.println("interrupting thread : " + thread.getName());
                            thread.interrupt();
                        }
                    }
                }
    
            };
            timer.schedule(timerTask, reaperInterval, reaperInterval);
        }
    
        public static void main(String args[]) throws Exception {
            TimedThreadPoolExecutor executor = new TimedThreadPoolExecutor(5,5, 1000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(20),
                    1000L,
                    200L);
    
            for (int i=0;i<10;i++) {
                executor.execute(new Runnable() {
                    public void run() {
                        try {
                            Thread.sleep(5000L);
                        }
                        catch (InterruptedException e) {
    
                        }
                    }
                });
            }
    
            executor.shutdown();
            while (! executor.isTerminated()) {
                executor.awaitTermination(1000L, TimeUnit.MILLISECONDS);
            }
        }
    
    
    
    }
    

    【讨论】:

    • 当然请注意,所有提交到该池的任务都有相同的超时时间。但是,只创建了 1 个额外的线程(不会做很多事情),从用户的角度来看,他们仍然只看到一个 ExecutorService。他们不必更改他们提交的 Callable。
    • 看起来是个不错的解决方案。最后关于你的 cmets,从 javadoc 我可以看到 isInterrupted() 在抛出异常时是cleared,所以检查它不会真的有帮助,对吗?
    • 另一个简单的问题,线程池中的线程被中断会发生什么?我在处理中断线程的 Java 源代码中看不到任何内容
    • 然后你检查 Thread.isInterrupted(),你应该很可能只是从你的代码中抛出一个 InterruptedException。但是,由于“run()”方法不允许这样做,您可能必须将它包装在 RuntimeException 中,或者记录它,然后立即返回。当线程池中的线程被中断时,这取决于您如何使用它。通常,线程会静默中断。但是,如果您从 run() 方法抛出异常,则您提交的 Future 对象(假设您使用了可调用对象,而不是可运行对象)将在调用 get 方法时抛出 ExecutionException。
    • 另外,我认为如果你真的想知道什么时候自动终止,你应该在上面的类中构建一个监听器,并在调用 Thread.interrupt( )。这可能会导致您重新考虑地图的内容,以便您可以获取特定线程中的 Runnable,或者您可以将 Thread 本身传递给侦听器。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-26
    • 2013-03-30
    • 1970-01-01
    • 2021-10-01
    • 2014-11-13
    • 2011-08-17
    • 1970-01-01
    相关资源
    最近更新 更多