Реализация пула потоков внутри службы

В настоящее время я работаю над реализацией 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();
    }
}

Итак, у меня есть кое-что, о чем я еще не уверен.

  • Предполагалось, что onDestroy был вызван, можно ли предположить, что моя реализация прервет ВСЕ выполняемые потоки и будет безопасно очищать ожидающие задачи без какого-либо прерывания с помощью реализации класса ThreadPoolExecutor? причина, по которой я спрашиваю, связана с тем, что очередь связана с исполнителем, и, возможно, shutdownNow является асинхронной и зависит от состояния очереди. Есть ли лучший способ сделать это?

  • Правильно ли я реализую эту логику внутри onDestroy? из моего опыта есть некоторые случаи, когда служба убита (т.е. низкая память), и этот обратный вызов не вызывается. Должен ли я выполнять аналогичный подход и в других местах?

  • Было бы лучше объявить члены класса очереди и исполнителя как статические? - Как указано в @TheTwo "Excecutor cannot be re-used once shutdown is called".

  • ThreadPoolExecutor класс ожидает BlockingQueue, каковы плюсы и минусы использования других типов реализаций BlockingQueue (т.е. ArrayBlockingQueue)?

  • Относительно того, как я в настоящее время обнаруживаю, когда очередь пуста, и больше нет ожидающих задач (в частности, внутри afterExecute) - это лучший способ сделать это? Или я могу получить указание, что очередь пуста, а задачи закончены по-другому?

Цените любую помощь!

Ответ 1

Я думаю, вы пытаетесь внедрить сервис, который представляет много проблем, но не решает. Эффективно вы уменьшаете код вызова на одну строку - создание Исполнителя - но убираете возможность его тонкого контроля. Нет никакой выгоды при планировании многих задач, поскольку это уже разрешено планировщиком потоков ОС. Кроме того, злоумышленник может разбить несколько других программ, просто добавив достаточно циклов while(true) sleep(100);.

По всем вопросам:

  • Вы не можете удостовериться, что все потоки должным образом прерваны, потому что нет возможности прервать поток, который неправильно просматривает флаг прерывания. A while(true) ; не может быть прервано, кроме как System.exit(). Теоретически вы можете остановить поток, но эта функция устарела по какой-то причине, потому что она может оставить фактическую задачу в незавершенном/незавершенном состоянии (то есть, оставляя TCP-соединения полуоткрытыми).

  • Нет, вы неправильно выполняете это. Если однажды задачи останутся int, то очередь просто исчезнет в пустоте, а затем Excecutor не сможет повторно использоваться после вызова shutdown. Поэтому вам, по крайней мере, нужно создать новый экземпляр Excecutor при запуске службы, и вам действительно нужно выяснить, что делать с оставшимися задачами.

  • Нет, из-за 2.

  • Преимущества/недостатки типов списков зависят от вашего прецедента. ArrayList имеет более высокую стоимость при росте/сокращении, но более низкую стоимость при индексировании определенного элемента (indexOf), а связанный список - наоборот. Поскольку ваша очередь всегда добавляет хвост, не заботятся ни о каком другом элементе, а о первом, и он часто растет/сокращается, связанный список - лучший выбор.

  • Вы не должны останавливать задачу таким образом вообще, потому что порядок выполнения с потоками undefined. В худшем случае ваша вызывающая программа прерывается каждый раз, пока услуга не будет выполнена, что приведет к тому, что служба будет постоянно запускаться и останавливаться без особых причин, тратя много времени на обработку впустую. Почему вы хотите остановить службу? Если ему нечего делать, это не будет делать ничего, кроме использования нескольких байтов памяти.

Ответ 2

Несколько лет назад я написал эту статью об управлении потоками в службе. Вы можете использовать само программное обеспечение или просто получить некоторые идеи о том, как сделать это самостоятельно.

Ответ 3

  • Нет. shutdownNow пытается прервать текущие активные задачи. Нет никаких гарантий, что он сможет это сделать.
  • Да, это так. Из документации:

    Как только ни одна из этих ситуаций не выполняется, служба onDestroy() вызывается метод, и служба фактически прекращается. Все очистка (остановка нитей, незарегистрированные приемники) должна быть завершена после возвращения из onDestroy().

  • нет причин, чтобы эти члены объявлялись как статические. Элемент static связан с классом withe, а не с каким-либо объектом, а использование common заключается в совместном использовании одного и того же статического члена между различными экземплярами (услугами в вашем случае).
  • Вы должны внимательно прочитать документацию обо всех возможных реализациях интерфейса BlockingQueue<E>, но я сомневаюсь, что для использования в обычных случаях вы увидите отличия от перспективы производительности.