【问题标题】:Java (Android) multi-threading processJava(Android)多线程进程
【发布时间】:2017-04-10 01:49:11
【问题描述】:

我正在开发应用程序(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 到 400ms,但一般小于 10ms

我在做什么

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 个 ping 响应,其余的被认为是由于超时而丢失。如果我增加迭代,损失也会增加(由于线程数的增加而呈指数增长)

观察:

我在 Galaxy S6 上运行这个应用程序,它有 8 个内核,应用程序池大小为 16,最大池大小为 16 + 2,我知道处理器一次只运行一个线程,它共享一个并行的量子时间处理。

通过及时观察ThreadPoolExecutor,我看到队列中有很多任务,超时后由于LinkedBlockingQueue,队列中仍然存在很多线程

如果我减少线程数它工作正常,但如果增加它会产生问题

问题:

  • 当我使用具有双核处理器的设备时,Ping 响应会降低。
  • 为什么队列中有很多线程,每个线程占用 大约 10 到 50 毫秒(增加线程时间会增加 uptp 300 毫秒或 更多)?
  • 它应该在给定的时间内完成,为什么不呢?
  • 如何解决这个问题?
  • 我应该使用ConcurrentLinkedQueue,但它使用生产者/消费者模型,不知何故ThreadPoolExecutor(我认为是)也使用此模型。
  • LinkedBlockingQueue 在任务执行之前持有任务(线程空闲或在队列中),如何克服这个问题?
  • 为后面的迭代设置Thread.MAX_PRIORITY 并不能解决问题(后面的迭代的线程在队列中)
  • 减少线程数可以解决问题,为什么?因为队列中没有线程少?
  • 有什么方法可以检查,如果队列中存在的线程招待他们,然后在不阻塞其他线程但在给定时间内执行其他线程。
  • 增加 5 秒这样的额外时间不是解决方案
  • How to get the ThreadPoolExecutor to increase threads to max before queueing? 一样更改 corePoolSize 在我的情况下不起作用。

在测试期间,内存和处理器的使用受到限制。

需要详细的答案/帮助。

编辑

当应用程序进入后台时,没有任何损失,用户 CPU 使用率下降到 0-2%,而焦点应用程序占用了 4-6% 的 cpu 使用率。是不是因为 UI 和其他乱七八糟的东西,我试图删除所有不必要的代码,我也确实将 PingThread 更改为 PingTask

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

注意: 我使用相同的代码创建了单独的基于 java 的应用程序,它在桌面上运行良好,所以我们可以说这是 android OS 特定的问题吗?

【问题讨论】:

  • 你不需要为每个子进程运行一个线程,所以我建议你从那里开始。此外,您的应用程序的核心功能是 ping 和路由跟踪,您应该考虑在进程中实现和执行这些任务。如果您异步执行此操作,则不需要线程或子进程,并且可以更好地控制取消。所有这些都将显着提高您的应用程序在规格较低的设备(CPU 速度、内存、网络带宽等)上的性能、可扩展性和电池使用率
  • 它的客户端要求在单独的线程中ping每个ip?你所说的过程中是什么意思?控制或取消不是这里的问题,因为我有有限的窗口来执行所有请求的 ping 并解析他们的响应,如果我这样做,损失将成倍增加
  • 请将您调用的方法的代码发布到PingThread。尤其是PingUtils.executePingRequest(request)方法
  • 您是否尝试过同时运行多个 AsyncTask?喜欢:task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  • @Stanojkovic AsyncTaskTHREAD_POOL_EXECUTOR 与 Threads 或 Runnable 相比具有权重,而默认 AsyncTask 使用 new LinkedBlockingQueue&lt;Runnable&gt;(128),如果为 AsyncTask 创建自定义池执行器,则不值得。当我们需要主动更新我们的 GUI 时,我们使用AsyncTask

标签: java android multithreading threadpoolexecutor blockingqueue


【解决方案1】:

线程创建一个新的唯一对象,而 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();
    }   
}

【讨论】:

  • 不相关的答案,请仔细阅读而不是回答任何stackoverflow问题
  • @Acapulco 线程创建唯一对象,而 runnable 将同一对象共享给多个线程
【解决方案2】:

观察:

在使用相同的代码创建和观察一个独立的java应用程序(日志)后,我知道了以下几点:

  • 不知何故,android 操作系统架构和/或处理器限制了线程数。
  • LinkedBlockingQueue 在任务执行之前保留任务,因此如果我们有一个很长的队列,那么队列中的后续线程将不得不等待更多时间。
  • Increase/Dynamic corePoolSizemaxPoolSize 也在做同样的事情,他们在队列中添加了线程
  • 应用程序使用 4-6% 的 CPU,因此我们不能说 CPU 过载或正在使用全部应用程序资源,但是当应用程序进入后台时(GUI 或其他相关操作系统和/或基于应用程序的线程可能停止或中断)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 个,这解决了问题。

【讨论】:

    【解决方案3】:

    我不确定这是否是导致所有问题的原因,但您正在创建 很多 不必要的线程。

    你应该替换

    private class PingThread extends Thread {
    

    与:

    private class PingThread implements Runnable {
    

    或(使用更恰当的名称):

    private class PingTask implements Runnable {
    

    即提交给Executors 的任务本身不应该是线程。它可以工作,因为 Thread 实现了 Runnable,但你在浪费它。

    【讨论】:

    • 这是一个好习惯,我使用线程来识别哪些线程需要时间来执行只是为了识别目的,我会将我的代码恢复为可运行但我认为这不会解决我的问题,是的,每个线程都会创建唯一的对象,而 runnable 将相同的对象共享给多个线程。
    • 您可以自己创建Thread 实例(直接或通过扩展它),也可以使用Executor,但这样做只是浪费(大量)资源。 ThreadPoolExecutor 使用它自己的线程,而不是你给它的线程。换句话说,PingThread 只是一个重量级的Runnable 实现。
    • 我会尽力让你知道
    • 如果增加线程池大小(例如 16 --> 32 或 48)会发生什么? ping 通常不是 CPU 密集型任务,池的线程大部分时间都在等待网络
    • 我按照上面帖子的建议尝试了 GrowBeforeQueueThreadPoolExecutor,但到目前为止还没有,你可以在我的帖子中看到 cpu 使用率在 4-6% 或小于 10% 但是当应用程序进入后台时 cpu 使用下降到 0-2%,所有任务在规定时间内完成
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    相关资源
    最近更新 更多