问题 在服务中实现线程池


我正在努力实现一个 Service 当被请求时将在几个并行线程上执行一些工作。

我的实现是基于 ThreadPoolExecutor 上课和一个 LinkedBlockingQueue

作为一项基本规则,一旦完成所有任务并且队列中没有待处理任务,我想停止服务(尽管稍后可以再次启动该服务并遵循相同的逻辑)。

我已经能够使用下面的代码达到预期的结果,但我不确定这种方法是否正确。

public class TestService extends Service {
    // Sets the initial threadpool size to 3
    private static final int CORE_POOL_SIZE = 3;

    // Sets the maximum threadpool size to 3
    private static final int MAXIMUM_POOL_SIZE = 3;

    // Sets the amount of time an idle thread will wait for a task before terminating
    private static final int KEEP_ALIVE_TIME = 1;

    // Sets the Time Unit to seconds
    private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;

    // A queue of Runnables for the uploading pool
    private final LinkedBlockingQueue<Runnable> uploadQueue = new LinkedBlockingQueue<Runnable>();

    // A managed pool of background upload threads
    private final ThreadPoolExecutor uploadThreadPool = new ThreadPoolExecutor(
            CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
            uploadQueue) {

        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);

            if (getActiveCount() == 1 && getQueue().size() == 0) {
                // we're the last Runnable around + queue is empty, service can be
                // safely stopped.
                TestService.this.stopSelf();
            }
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // execute a new Runnable
        uploadThreadPool.execute(new TestRunnable());

        /**
         * Indicating that if Android has to kill off this service (i.e. low memory),
         * it should not restart it once conditions improve.
         */
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        uploadThreadPool.shutdownNow();
        uploadQueue.clear();

        super.onDestroy();
    }
}

所以我有一些我还不确定的事情。

  1. 假设 onDestroy 被调用,是否可以安全地假设我的实现将中断所有正在运行的线程并安全地清除待处理的任务而不会以某种方式中断 ThreadPoolExecutor 类实现?我问的原因是因为队列与执行者有关,也许 shutdownNow 是异步的,取决于队列的状态。有更好的方法吗?

  2. 我正确地在里面实现这个逻辑 onDestroy?根据我的经验,在某些情况下服务被杀死(即内存不足),并且不会调用此回调。我是否应该在其他地方采用类似的方法?

  3. 将我的队列和执行程序类成员声明为静态会更好吗?  - 正如@TheTwo所述 "Excecutor cannot be re-used once shutdown is called"

  4. ThreadPoolExecutor 上课期望一个 BlockingQueue,使用其他类型的利弊是什么? BlockingQueue 实施(即 ArrayBlockingQueue)?

  5. 关于我当前检测队列为空的方式,并且没有更多待处理的任务(特别是在内部 afterExecute 回调) - 这是最好的方法吗?或者我可以得到队列为空并且任务以其他方式完成的指示吗?

感谢任何帮助!


6448
2018-01-22 08:29


起源



答案:


我认为你正在尝试实现一项服务,它引入了许多问题,但没有解决。实际上,您可以将调用代码减少一行 - 执行程序的创建 - 但是会删除对其进行精细控制的能力。调度许多任务没有任何好处,因为这已经由OS的线程调度程序解决了。此外,恶意调用者可以通过添加足够的内容来破坏其他几个程序 while(true) sleep(100); 循环。

关于你的问题:

  1. 您无法确保所有线程都被正确中断,因为无法中断未正确观察中断标志的线程。一个 while(true) ; 不能被打断除外 System.exit()。理论上你可以停止一个线程,但是这个特性由于某种原因而被弃用,因为它可以使实际任务处于不完整/未完成状态(即使是半连接的TCP连接)。

  2. 不,你没有正确实现它。对于一旦剩下的任务,队列就会在虚空中消失,然后一旦调用shutdown就不能重新使用Excecutor。所以你至少需要在服务启动时创建一个新的Excecutor实例,你真的应该弄清楚如何处理剩余的任务。

  3. 不,因为2。

  4. 列表类型的优缺点取决于您的用例。 ArrayList在增长/收缩时成本较高,但在索引特定元素(indexOf)时成本较低,而链表则相反。由于你的队列总是添加到尾部,不关心任何其他元素而是第一个,并且它经常增长/缩小,链接列表是最佳选择。

  5. 您根本不应该以这种方式停止任务,因为未定义线程的执行顺序。在最坏的情况下,您的调用程序每次都会被中断,直到服务执行完毕,这将导致服务无缘无故地启动和停止,同时浪费了大量的处理时间。为什么你甚至想要停止服务?如果它无关,除了使用几个字节的内存之外它什么都不做。


5
2018-01-25 17:30



#5,下面 youtube.com/watch?v=xHXn3Kg2IQE 我一直认为,一旦服务完成它的工作,它应该停止,以保持电池并通知系统它可以释放资源,并且该服务不再使用..不是android制作有关根据此类信息销毁特定运行组件的决定? - cdroid
如果您打算再也不使用该服务,最好将其清理干净。但想象如下: for (int i=0; i < myTasks.length; ++i) service.run(myTasks[i]); 在最坏的情况下,这将破坏并重建服务100次,因为线程不保证有关执行顺序的任何内容。 - TwoThe


答案:


我认为你正在尝试实现一项服务,它引入了许多问题,但没有解决。实际上,您可以将调用代码减少一行 - 执行程序的创建 - 但是会删除对其进行精细控制的能力。调度许多任务没有任何好处,因为这已经由OS的线程调度程序解决了。此外,恶意调用者可以通过添加足够的内容来破坏其他几个程序 while(true) sleep(100); 循环。

关于你的问题:

  1. 您无法确保所有线程都被正确中断,因为无法中断未正确观察中断标志的线程。一个 while(true) ; 不能被打断除外 System.exit()。理论上你可以停止一个线程,但是这个特性由于某种原因而被弃用,因为它可以使实际任务处于不完整/未完成状态(即使是半连接的TCP连接)。

  2. 不,你没有正确实现它。对于一旦剩下的任务,队列就会在虚空中消失,然后一旦调用shutdown就不能重新使用Excecutor。所以你至少需要在服务启动时创建一个新的Excecutor实例,你真的应该弄清楚如何处理剩余的任务。

  3. 不,因为2。

  4. 列表类型的优缺点取决于您的用例。 ArrayList在增长/收缩时成本较高,但在索引特定元素(indexOf)时成本较低,而链表则相反。由于你的队列总是添加到尾部,不关心任何其他元素而是第一个,并且它经常增长/缩小,链接列表是最佳选择。

  5. 您根本不应该以这种方式停止任务,因为未定义线程的执行顺序。在最坏的情况下,您的调用程序每次都会被中断,直到服务执行完毕,这将导致服务无缘无故地启动和停止,同时浪费了大量的处理时间。为什么你甚至想要停止服务?如果它无关,除了使用几个字节的内存之外它什么都不做。


5
2018-01-25 17:30



#5,下面 youtube.com/watch?v=xHXn3Kg2IQE 我一直认为,一旦服务完成它的工作,它应该停止,以保持电池并通知系统它可以释放资源,并且该服务不再使用..不是android制作有关根据此类信息销毁特定运行组件的决定? - cdroid
如果您打算再也不使用该服务,最好将其清理干净。但想象如下: for (int i=0; i < myTasks.length; ++i) service.run(myTasks[i]); 在最坏的情况下,这将破坏并重建服务100次,因为线程不保证有关执行顺序的任何内容。 - TwoThe


几年前我写道 本文 管理服务中的线程。您可以使用该软件本身,或者只是获得一些关于如何自己动手的想法。


4
2018-01-22 15:44



请将文章中的一些细节添加到您的答案中。如果链接失效,它将使您的答案无用。 - Luksprog
问题在于文章解释了为什么需要经理和良好的软件解决方案的背景。添加细节意味着这里有很多单词而没有与软件的链接它没有价值。 - edharned


  1. No。shutdownNow尝试中断当前正在执行的任务。无法保证能够做到这一点
  2. 是的。从文档:

一旦这些情况都不成立,服务的onDestroy()   调用方法并有效终止服务。所有   应该完成清理(停止线程,取消注册接收器)   从onDestroy()返回时。


2
2018-01-22 08:58



至于4,你可以考虑使用 Executors 实用类(见 docs.oracle.com/javase/7/docs/api/java/util/concurrent/...)创建ThreadPoolExecutor实例。例如 Executors#newFixedThreadPool(int nThreads) - Tomasz Gawel
@blackbelt我之所以要问#4是因为服务可以多次停止和启动,也许能够使用同一个实例。 - cdroid
这通常不会带来任何结果,但是,也许这位下来的人会留下评论,以便我可以改进我的答案? - Blackbelt