import android.annotation.WorkerThread;
import java.util.ArrayDeque;
-import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
- private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
- // We want at least 2 threads and at most 4 threads in the core pool,
- // preferring to have 1 less than the CPU count to avoid saturating
- // the CPU with background work
- private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
- private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
- private static final int KEEP_ALIVE_SECONDS = 30;
+ // We keep only a single pool thread around all the time.
+ // We let the pool grow to a fairly large number of threads if necessary,
+ // but let them time out quickly. In the unlikely case that we run out of threads,
+ // we fall back to a simple unbounded-queue executor.
+ // This combination ensures that:
+ // 1. We normally keep few threads (1) around.
+ // 2. We queue only after launching a significantly larger, but still bounded, set of threads.
+ // 3. We keep the total number of threads bounded, but still allow an unbounded set
+ // of tasks to be queued.
+ private static final int CORE_POOL_SIZE = 1;
+ private static final int MAXIMUM_POOL_SIZE = 20;
+ private static final int BACKUP_POOL_SIZE = 5;
+ private static final int KEEP_ALIVE_SECONDS = 3;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
}
};
- private static final BlockingQueue<Runnable> sPoolWorkQueue =
- new LinkedBlockingQueue<Runnable>(128);
+ // Used only for rejected executions.
+ // Initialization protected by sRunOnSerialPolicy lock.
+ private static ThreadPoolExecutor sBackupExecutor;
+ private static LinkedBlockingQueue<Runnable> sBackupExecutorQueue;
+
+ private static final RejectedExecutionHandler sRunOnSerialPolicy =
+ new RejectedExecutionHandler() {
+ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
+ android.util.Log.w(LOG_TAG, "Exceeded ThreadPoolExecutor pool size");
+ // As a last ditch fallback, run it on an executor with an unbounded queue.
+ // Create this executor lazily, hopefully almost never.
+ synchronized (this) {
+ if (sBackupExecutor == null) {
+ sBackupExecutorQueue = new LinkedBlockingQueue<Runnable>();
+ sBackupExecutor = new ThreadPoolExecutor(
+ BACKUP_POOL_SIZE, BACKUP_POOL_SIZE, KEEP_ALIVE_SECONDS,
+ TimeUnit.SECONDS, sBackupExecutorQueue, sThreadFactory);
+ sBackupExecutor.allowCoreThreadTimeOut(true);
+ }
+ }
+ sBackupExecutor.execute(r);
+ }
+ };
/**
* An {@link Executor} that can be used to execute tasks in parallel.
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
- sPoolWorkQueue, sThreadFactory);
- threadPoolExecutor.allowCoreThreadTimeOut(true);
+ new SynchronousQueue<Runnable>(), sThreadFactory);
+ threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}