Подтвердить что ты не робот

Многопоточный процесс Java (Android)

Я работаю над приложением (Matt traceroute версия для Windows http://winmtr.net/), которая создает несколько потоков в каждом потоке имеет свой собственный процесс (который выполняет команду ping). ThreadPoolExecutor выключение всех потоков через некоторое время (например, 10 секунд)

ThreadPoolExecutor использует блокирующую очередь (выполнение задач перед их выполнением)

int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
    NUMBER_OF_CORES * 2, NUMBER_OF_CORES * 2 + 2, 10L, TimeUnit.SECONDS, 
    new LinkedBlockingQueue<Runnable>()
);

PingThread.java

private class PingThread extends Thread {

    @Override
    public void run() {
        long pingStartedAt = System.currentTimeMillis();
        // PingRequest is custom object
        PingRequest request = buildPingRequest(params);

        if (!isCancelled() && !Thread.currentThread().isInterrupted()) {

            // PingResponse is custom object

            // Note:
            // executePingRequest uses PingRequest to create a command 
            // which than create a runtime process to execute ping command
            // using string response i am creating PingResponse

            PingResponse pingResponse = PingUtils.executePingRequest(request);

            if (pingResponse != null) {
                pingResponse.setHopLocation(hopLocation);                   
                // publish ping response to main GUI/handler
                publishProgress(pingResponse);
            } else
                Logger.error(
                    "PingThread", "PingResponse isNull for " + request.toString()
                );
        }
    }
}

Теперь, если я создаю несколько потоков, скажем более 500 в цикле и выполняю внутренний исполнитель пула

Выполнение потоков

PingThread thread = new PingThread(params);
poolExecutor.execute(thread);

Я знаю, что LinkedBlockingQueue выполняет задачи перед их выполнением. Каждый процесс потока занимает от 200 до 400 мс, но обычно он меньше 10 мс

Что я делаю

for (int iteration = 1; iteration <= 50/*configurable*/; iteration++) {

    for (int index = 0; index < 10/*configurable*/; index++) {
        PingThread thread = new PingThread(someParams);
        poolExecutor.execute(thread);
    }

    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        Logger.error(false, e);
    }   
}

50 итераций займут около 25 секунд, здесь у меня есть только до 40 ответов на пинг, считающиеся потерей из-за тайм-аута. Если я увеличиваю итерации, также увеличивается потеря (экспоненциально из-за увеличения количества потоков)

Замечание:

Я запускаю это приложение на Galaxy S6 с 8 ядрами, размер пула приложений - 16, а максимальный размер пула - 16 + 2, я знаю, что процессор работает только по одному потоку за раз, он разделяет квантовое время для параллельного обработка.

Наблюдая ThreadPoolExecutor своевременно, я вижу много задач в очереди, после таймаута в очереди все еще много потоков из-за LinkedBlockingQueue

Если я уменьшаю ни один из потоков, он отлично работает, но если его увеличение создает проблему

Проблема:

  • Ответы Ping уменьшаются, когда я использую устройства с двухъядерным процессором.
  • Почему в очереди присутствует много потоков, где каждый поток принимает от 10 до 50 мс (увеличение времени потока увеличится до 300 мс или больше)?
  • Это должно завершиться в заданное время, почему его нет?
  • Как решить эту проблему?
  • Должен ли я использовать ConcurrentLinkedQueue, но он использует модель Producer/consumer, как-то ThreadPoolExecutor (я думаю, что это) тоже использует эту модель.
  • LinkedBlockingQueue выполняет задачи перед их выполнением (потоки простаивают или находятся в очереди), как это преодолеть?
  • Установив Thread.MAX_PRIORITY для последних, итерации не решают проблему (более поздний итерационный поток находится в очереди)
  • Уменьшение количества потоков решает проблему, почему? потому что в очереди меньше потоков в очереди?
  • Есть ли способ проверить, если потоки, присутствующие в очереди, их развлекают, а затем выполнять другие, не блокируя другие потоки, но в течение заданного времени.
  • Добавление дополнительного времени, такого как 5 секунд, не является решением
  • Изменение corePoolSize как в Как заставить ThreadPoolExecutor увеличить потоки до max перед очередью? не работает в моем случае.

Во время тестирования использование памяти и процессора ограничено.

Требуется подробный ответ/справка.

Edit

Когда приложение переходит в фоновый режим, потери и загрузка процессора пользователя снижаются до 0-2%, в то время как на приложение фокуса - 4-6% использования процессора. Является ли это результатом UI и других правдоподобных вещей, я попытался удалить все ненужные коды, и я изменил PingThread на PingTask

PingTask implements Runnable {/*....*/}

Примечание: Я создал отдельное приложение на основе Java, используя тот же код, и он отлично работает на рабочем столе, поэтому можем ли мы сказать об особой проблеме ОС Android?

4b9b3361

Ответ 1

Замечание:

После создания и наблюдения за независимым java-приложением (журналами) с использованием того же кода я узнал следующее:

  • Как-то архитектура и/или процессорная архитектура Android ограничивают количество потоков.
  • LinkedBlockingQueue содержит задачи перед их выполнением, поэтому, если у нас есть длинная очередь, то в очереди очереди в очереди будут ждать больше.
  • Увеличение/Динамическое corePoolSize и maxPoolSize делают то же самое, они добавили потоки в очередь
  • Приложение использует 4-6% CPU, поэтому мы не можем сказать, что процессор перегружает или использует полные ресурсы приложений, но когда приложение переходит в фоновый режим (графический интерфейс или другие связанные потоки ОС и/или приложения могут останавливаться или прерываться). падает до 0-3%.

Решение:

Для 50 итераций и 10 внутренних создает 500 потоков, теперь я сделал две вещи:

  • Увеличение Thread.sleep(millis) времени при некотором вычислении включает.
  • Уменьшить количество потоков для каждой итерации. Я создал 10 потоков сейчас Math.ceil((double) 10 / 3) = 3, поэтому у нас есть 3 последовательных PingUtils.executePingRequest(pingRequest) для каждого потока, то есть 3 * 3 = 9 остается 1, поэтому мы создадим отдельный поток для последнего запроса. Для каждой итерации вместо создания 10 потоков теперь я создаю 4 потока.
  • Используя этот подход, теперь у меня есть 200 потоков вместо 500, которые решают проблему.

Ответ 2

Я не уверен, что это вызывает все проблемы, но вы создаете много ненужных потоков.

Вы заменяете

private class PingThread extends Thread {

с:

private class PingThread implements Runnable {

или (используя более подходящее имя):

private class PingTask implements Runnable {

то есть. задачи, отправленные на Executor, не должны быть самими потоками. Он работает, потому что Thread реализует Runnable, но вы его теряете.

Ответ 3

Темы создают новый уникальный объект, а runnable позволяет всем потокам совместно использовать один объект. Таким образом, вы не должны распространять Thread при попытке многопоточности, вместо этого используйте Runnable:

class RunnableDemo implements Runnable {
    private Thread thread;
    String threadName="My thread";
    public void run() {
        //do your code from here 'logic'
        System.out.println("Threading is Running");
        try {
            for(int i = 4; i > 0; i--) {
                System.out.println("Thread: "+threadName +" "+ i);
                // Let the thread sleep for a while.
                Thread.sleep(50); //sleep your content for xx miliseconds
            }
        } catch (InterruptedException e) {
            System.out.println("Thread " +  threadName + " interrupted.");
        }
        System.out.println("Thread " +  threadName + " exiting.");
        //finish your work here 
    }

    public void start () {
        System.out.println("Starting " +  threadName );
        if (thread == null) {
            thread = new Thread (this);
            thread.start (); //This will call your run methods of Runnable
        }
    }
}
//test your thread from here
public class TestThread {
    public static void main(String args[]) {
        RunnableDemo R1 = new RunnableDemo( "Thread-1");
        R1.start();

        RunnableDemo R2 = new RunnableDemo( "Thread-2");
        R2.start();
    }   
}