13

I am working on application (Matt's traceroute windows version http://winmtr.net/) which creates multi threads each thread has its own process (which execute ping command). ThreadPoolExecutor shutdown all threads after some time( e.g.10 seconds)

ThreadPoolExecutor uses blocking queue(holding tasks before they executed)

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()
                );
        }
    }
}

Now if i create multiple threads say more than 500 in a loop and execute inside pool executor

Executing Threads

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

I do know that LinkedBlockingQueue holds tasks before they executed. Each thread's process takes maximum 200 to 400ms but generally it is less than 10ms

What i am doing

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 iterations will take about 25 seconds, here i only have up-to 40 ping responses rest consider as loss due to time out. If i increase iterations loss increases too (exponentially due to increase in no of threads)

Observation:

I am running this application on Galaxy S6 which has 8 cores, application pool size is 16 and maximum pool size is 16 + 2, i do know that processor runs only one thread at a time, it shares a quantum time for parallel processing.

By observing ThreadPoolExecutor on timely basis, i see many tasks in queue, after timeout there are still many threads present in queue due to LinkedBlockingQueue

If i decrease no of threads it works fine but if increase it creates problem

Problem:

  • Ping responses decreases when i use devices with dual core processor.
  • Why there are many threads present in queue, where each thread takes about 10 to 50ms (increase threads time will increase uptp 300ms or more)?
  • It should complete with in given time, why its not?
  • How to overcome this problem?
  • Should i use ConcurrentLinkedQueue but it uses Producer/consumer model, somehow ThreadPoolExecutor (i think it is) uses this model too.
  • LinkedBlockingQueue holds tasks before they executed (threads are idle or in queue), how to overcome this?
  • By setting Thread.MAX_PRIORITY for latter iterations does't solve the problem (later iteration's thread are in queue)
  • Decreasing no of threads solves the problem why? because there are less no threads present in queue?
  • Is there any way to check, if threads present in queue entertain them then execute other, without blocking other threads but within given time.
  • Adding extra time like 5 seconds is not a solution
  • Changing corePoolSize like in How to get the ThreadPoolExecutor to increase threads to max before queueing? is not working in my case.

During testing Memory and Processor usage are with in a limit.

Detail answer/help is required.

Edit

When application goes into background there is no loss and user CPU usage drops to 0-2% while on focus app took 4-6% of cpu usage. Is it due to UI and other ralted stuff, i tried to remove all unnecessary code also i did change PingThread to PingTask

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

Note: I created separate java based application using same code and it works fine on desktop, so can we say it's android OS specific issue?

Community
  • 1
  • 1
Haris Qurashi
  • 2,104
  • 1
  • 13
  • 28
  • You don't need to run a thread for each child process, so I suggest you start from there. Also, your application's core feature is pinging and route tracing, you should consider implementing and performing these tasks in-process. If you do so asynchronously, you don't need either threads or child processes, and you can control cancellation much better. All together will noticeably improve your application's performance, scalability and battery usage on devices with lower specifications (CPU speed, memory, network bandwidth, etc.) – acelent Nov 30 '16 at 10:52
  • Its client requirement to ping each ip in separate thread? And what you mean by in-process? Controlling or cancelling isn't the issue here its because i have limited windows to execute all ping requested and parse their responses, if i do this sequentially loss will increase exponentially – Haris Qurashi Nov 30 '16 at 11:16
  • Please post the code of the methods which you invoke in `PingThread`. Especially the `PingUtils.executePingRequest(request)` method – Vasiliy Nov 30 '16 at 19:57
  • Have you try to run multi AsyncTask at the same time? Like: `task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);` – Stanojkovic Dec 01 '16 at 13:50
  • @Stanojkovic `AsyncTask`'s `THREAD_POOL_EXECUTOR` is weighted as compared to Threads or Runnable while default `AsyncTask` uses `new LinkedBlockingQueue(128)`, if create a custom pool executor for `AsyncTask` its not worth. We use `AsyncTask` when we need to actively update our GUI – Haris Qurashi Dec 01 '16 at 14:47
  • Can you explain the purpose of the `Thread.sleep(500);`? Not sure why this is there. – petey Dec 01 '16 at 15:26
  • @petey its a client requirement to wait after each iteration,If we increase wait from 500 to 600 or even a bit more than we have satisfying result – Haris Qurashi Dec 01 '16 at 15:53
  • You said: "Decreasing no of threads solves the problem why?" so you solved it, just optimize the number of threads! And you know why, because you have restricted resources. Also you said: " created separate java based application using same code and it works fine on desktop". Obviously on desktop you have more resources. – hadilq Dec 03 '16 at 09:23

3 Answers3

5

I am not sure if this is what causes all the problems, but you are creating a lot of unnecessary threads.

You shoud replace

private class PingThread extends Thread {

with :

private class PingThread implements Runnable {

or (using a more adequate name) :

private class PingTask implements Runnable {

i.e. the tasks submited to Executors are not supposed to be thread themselves. It works, because a Thread implements Runnable, but you are wasting it.

bwt
  • 17,292
  • 1
  • 42
  • 60
  • It is good practice, i am using threads to identify which threads takes time for execution just for identification purpose, i will revert my code back to runnable but i don't think this will solve my problem, yeah each thread creates unique object whereas runnable shares the same object to multiple threads. – Haris Qurashi Nov 29 '16 at 17:24
  • You can either create `Thread` instances (directly or by extending it) yourself, or use an `Executor` but doing both is just wasting (a lot) of resources. The `ThreadPoolExecutor` uses it's own threads, not the ones you are giving it. In other words the `PingThread` is just a very heavy weight `Runnable` implementation. – bwt Nov 29 '16 at 17:35
  • I will try and let you know – Haris Qurashi Nov 29 '16 at 17:42
  • what happen if you increase the threadpool size (e.g. 16 --> 32 or 48) ? A ping is usually not a CPU intensive task, the pool's threads spend most of the time waiting for the network – bwt Nov 30 '16 at 12:42
  • I tried GrowBeforeQueueThreadPoolExecutor as suggested by above post, but nothing so far, you can see in my post cpu use is in between 4-6% or less than 10% but when app goes to background cpu uses drops to 0-2% and all tasks completed within time – Haris Qurashi Nov 30 '16 at 12:47
  • I am not sure I undestand correctly, in your case the core size is 16 and the max is 18 so 'grow to max' does not change much the thread number. Did you try to put both core and max to, say 50 ? It is probably not a good idea in general, but it just to be sure that the work is actually paralellized 50 times – bwt Nov 30 '16 at 13:20
  • Yes i tried with corePoolSize=8, maxCorePool=50 but can't find a huge difference – Haris Qurashi Nov 30 '16 at 13:23
  • and with corePoolSize = 50 and maxCorePool = 50 ? – bwt Nov 30 '16 at 15:30
  • well, changing all parameters controlling the threading does not change anything. I can't think of anything more to check. Sorry I couldn't help you. – bwt Dec 01 '16 at 12:59
  • its ok i am stuck too :( – Haris Qurashi Dec 01 '16 at 13:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/129550/discussion-between-haris-qureshi-and-bwt). – Haris Qurashi Dec 01 '16 at 14:53
3

Threads create a new unique object, while runnable allows all the threads to share one object. As such, you should not extend Thread when trying to multithread, instead use 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();
    }   
}
Zoe
  • 27,060
  • 21
  • 118
  • 148
3

Observation:

After creating and observing a independent java application (logs) using same code, I came to know the followings:

  • Somehow android OS architecture and/or processor is limiting the number of threads.
  • LinkedBlockingQueue holds tasks before they executed, so if we have a long queue later threads in queue will have to wait more.
  • Increase/Dynamic corePoolSize and maxPoolSize is doing the same, they added threads in queue
  • Application uses 4-6% of CPU so we can't say CPU is overloading or utilizing full application resources but when application goes into background (GUI or other related OS and/or application based threads may stop or interrupted) CPU uses drops to 0-3%.

Solution:

For 50 iterations and 10 inner creates 500 threads now i did two things:

  • Increase Thread.sleep(millis) time with some calculation involves.
  • Decrease number of threads for each iteration. I was creating 10 threads now Math.ceil((double) 10 / 3) = 3 so we have 3 sequential PingUtils.executePingRequest(pingRequest) for each thread i.e. 3 * 3 = 9 remains 1 so we will create a separate thread for last request. For each iteration instead of creating 10 threads now i am creating 4 threads.
  • Using this approach now i have 200 threads instead of 500 which solves the issue.
Gurwinder Singh
  • 38,557
  • 6
  • 51
  • 76
Haris Qurashi
  • 2,104
  • 1
  • 13
  • 28