Java(Android)多线程进程

时间:2016-11-25 16:00:32

标签: java android multithreading threadpoolexecutor blockingqueue

我正在开发应用程序( 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个ping响应,其余因为超时而被视为损失。如果我增加迭代损失也会增加(由于线程数没有增加而呈指数增长)

观察:

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

通过及时观察ThreadPoolExecutor,我看到许多任务在队列中,超时后,由于LinkedBlockingQueue

,队列中仍然存在许多线程

如果我减少了没有线程,它可以正常工作,但如果增加则会产生问题

问题:

  • 当我使用具有双核处理器的设备时,Ping响应会减少。
  • 为什么队列中存在许多线程,每个线程占用 约10至50毫秒(增加线程时间将增加300毫秒或 更多)?
  • 它应该在给定的时间内完成,为什么不呢?
  • 如何克服这个问题?
  • 我应该使用ConcurrentLinkedQueue但是它使用生产者/消费者模型,不知怎的ThreadPoolExecutor(我认为是)也使用这个模型。
  • LinkedBlockingQueue在执行任务之前保存任务(线程空闲或在队列中),如何克服这个问题?
  • 通过为后面的迭代设置Thread.MAX_PRIORITY不能解决问题(后来的迭代&#39; s线程在队列中)
  • 减少线程没有解决问题的原因?因为队列中没有线程存在?
  • 有没有办法检查,如果队列中存在的线程招待他们然后执行其他线程,而不是在给定时间内阻止其他线程。
  • 添加5秒等额外时间不是解决方案
  • How to get the ThreadPoolExecutor to increase threads to max before queueing?中更改corePoolSize在我的情况下不起作用。

测试期间,内存和处理器的使用情况有限制。

需要详细解答/帮助。

修改

当应用程序进入后台时,没有丢失,用户CPU使用率下降到0-2%,而焦点应用程序占用了4-6%的CPU使用率。是因为UI和其他ralted东西,我试图删除所有不必要的代码,我也改变了PingThreadPingTask

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

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

3 个答案:

答案 0 :(得分:5)

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

你应该替换

[{"group_id":87,"group_name":"\u06af\u0632\u06cc\u0646\u0647 \u0647\u0627\u06cc \u0639\u0645\u0648\u0645\u06cc","group_slug":"%da%af%d8%b2%db%8c%d9%86%d9%87-%d9%87%d8%a7%db%8c-%d8%b9%d9%85%d9%88%d9%85%db%8c","group_desc":"","attributes":[{"attr_id":95,"attr_name":"\u062a\u0627\u0631\u06cc\u062e \u0627\u0646\u062a\u0634\u0627\u0631","attr_slug":"%d8%aa%d8%a7%d8%b1%db%8c%d8%ae-%d8%a7%d9%86%d8%aa%d8%b4%d8%a7%d8%b1","attr_desc":"","value":"144"},{"attr_id":96,"attr_name":"\u0648\u0636\u0639\u06cc\u062a \u0628\u0627\u0632\u0627\u0631","attr_slug":"stock","attr_desc":"","value":"instock"},{"attr_id":99,"attr_name":"\u0628\u0644\u0648\u062a\u0648\u062b","attr_slug":"%d8%a8%d9%84%d9%88%d8%aa%d9%88%d8%ab","attr_desc":"","value":"yes"},{"attr_id":100,"attr_name":"\u0648\u0627\u06cc \u0641\u0627\u06cc","attr_slug":"%d9%88%d8%a7%db%8c-%d9%81%d8%a7%db%8c","attr_desc":"","value":"no"}]},{"group_id":89,"group_name":"\u0635\u0641\u062d\u0647 \u0646\u0645\u0627\u06cc\u0634","group_slug":"%d8%b5%d9%81%d8%ad%d9%87-%d9%86%d9%85%d8%a7%db%8c%d8%b4","group_desc":"","attributes":[]},{"group_id":57,"group_name":"\u067e\u0631\u062f\u0627\u0632\u0646\u062f\u0647","group_slug":"%d9%be%d8%b1%d8%af%d8%a7%d8%b2%d9%86%d8%af%d9%87","group_desc":"","attributes":[]}]

with:

private class PingThread extends Thread {

或(使用更合适的名称):

private class PingThread implements Runnable {

即。提交给private class PingTask implements Runnable { 的任务不应该是自己的线程。它有效,因为Executor实现了Thread,但是你在浪费它。

答案 1 :(得分: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();
    }   
}

答案 2 :(得分:3)

观察:

使用相同的代码创建和观察独立的java应用程序(日志)后,我开始了解以下内容:

  • 不知何故,android OS架构和/或处理器限制了线程数。
  • LinkedBlockingQueue在执行任务之前保存任务,因此如果我们有一个长队列,队列中的线程将不得不等待更多。
  • 增加/动态corePoolSizemaxPoolSize也是这样,他们在队列中添加了线程
  • 应用程序使用4-6%的CPU,因此我们不能说CPU过载或使用完整的应用程序资源但是当应用程序进入后台时(GUI或其他相关的OS和/或基于应用程序的线程可能会停止或中断) 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个解决了这个问题。