Executors類提供了4種不同的線程池:newCachedThreadPool绩蜻、newFixedThreadPool缺狠、 newScheduledThreadPool和newSingleThreadExecutor,它們都是直接或間接通過ThreadPoolExecutor實(shí)現(xiàn)的对蒲。
*ThreadPoolExecutor:
// Public constructors and methods
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the {@link Executors} factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
ThreadPoolExecutor的構(gòu)造方法有以下幾個(gè)重要參數(shù):
corePoolSize:核心線程數(shù)赚导。核心線程會(huì)在線程池中一直存活蝗蛙,即時(shí)它們處于閑置狀態(tài),例外情況是allowCoreThreadTimeOut被設(shè)置為true敲长。
maximumPoolSize:最大線程數(shù)郎嫁。
keepAliveTime:線程閑置時(shí)的超時(shí)時(shí)長秉继,超時(shí)后線程會(huì)被回收。
unit:keepAliveTime的時(shí)間單位泽铛。
workQueue:存放等待執(zhí)行的任務(wù)的阻塞隊(duì)列尚辑。
隊(duì)列與線程池按照以下規(guī)則進(jìn)行交互:
如果運(yùn)行的線程數(shù)小于核心線程數(shù)(corePoolSize),則首選添加新線程而不排隊(duì)盔腔。如果運(yùn)行的線程數(shù)等于或者大于核心線程數(shù)(corePoolSize)杠茬,則首選將請求加入隊(duì)列而不添加新線程。如果請求無法加入隊(duì)列弛随,則創(chuàng)建新線程瓢喉;如果這將導(dǎo)致超出最大線程數(shù)(maximumPoolSize),則任務(wù)將被拒絕執(zhí)行舀透。
*Executors:
/**
* Creates a thread pool that reuses a fixed number of threads
* operating off a shared unbounded queue. At any point, at most
* {@code nThreads} threads will be active processing tasks.
* If additional tasks are submitted when all threads are active,
* they will wait in the queue until a thread is available.
* If any thread terminates due to a failure during execution
* prior to shutdown, a new one will take its place if needed to
* execute subsequent tasks. The threads in the pool will exist
* until it is explicitly {@link ExecutorService#shutdown shutdown}.
*
* @param nThreads the number of threads in the pool
* @return the newly created thread pool
* @throws IllegalArgumentException if {@code nThreads <= 0}
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到FixedThreadPool只有固定數(shù)量的核心線程栓票,任務(wù)隊(duì)列是基于鏈表的無界阻塞隊(duì)列。當(dāng)所有線程都在運(yùn)行時(shí)愕够,新任務(wù)都會(huì)放到任務(wù)隊(duì)列中等待逗载。
默認(rèn)情況下,Glide的網(wǎng)絡(luò)請求是在EngineJob中的sourceExecutor中執(zhí)行的链烈,而這個(gè)sourceExecutor是通過GlideExecutor的newSourceExecutor方法實(shí)例化的厉斟。
*GlideExecutor:
/**
* Returns a new fixed thread pool with the default thread count returned from
* {@link #calculateBestThreadCount()}, the {@link #DEFAULT_SOURCE_EXECUTOR_NAME} thread name
* prefix, and the
* {@link com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy#DEFAULT}
* uncaught throwable strategy.
*
* <p>Source executors allow network operations on their threads.
*/
public static GlideExecutor newSourceExecutor() {
return newSourceExecutor(calculateBestThreadCount(), DEFAULT_SOURCE_EXECUTOR_NAME,
UncaughtThrowableStrategy.DEFAULT);
}
/**
* Returns a new fixed thread pool with the given thread count, thread name prefix,
* and {@link com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy}.
*
* <p>Source executors allow network operations on their threads.
*
* @param threadCount The number of threads.
* @param name The prefix for each thread name.
* @param uncaughtThrowableStrategy The {@link
* com.bumptech.glide.load.engine.executor.GlideExecutor.UncaughtThrowableStrategy} to use to
* handle uncaught exceptions.
*/
public static GlideExecutor newSourceExecutor(int threadCount, String name,
UncaughtThrowableStrategy uncaughtThrowableStrategy) {
return new GlideExecutor(threadCount, name, uncaughtThrowableStrategy,
false /*preventNetworkOperations*/, false /*executeSynchronously*/);
}
// Visible for testing.
GlideExecutor(int poolSize, String name,
UncaughtThrowableStrategy uncaughtThrowableStrategy, boolean preventNetworkOperations,
boolean executeSynchronously) {
this(
poolSize /* corePoolSize */,
poolSize /* maximumPoolSize */,
0 /* keepAliveTimeInMs */,
name,
uncaughtThrowableStrategy,
preventNetworkOperations,
executeSynchronously);
}
GlideExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTimeInMs, String name,
UncaughtThrowableStrategy uncaughtThrowableStrategy, boolean preventNetworkOperations,
boolean executeSynchronously) {
this(
corePoolSize,
maximumPoolSize,
keepAliveTimeInMs,
name,
uncaughtThrowableStrategy,
preventNetworkOperations,
executeSynchronously,
new PriorityBlockingQueue<Runnable>());
}
GlideExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTimeInMs, String name,
UncaughtThrowableStrategy uncaughtThrowableStrategy, boolean preventNetworkOperations,
boolean executeSynchronously, BlockingQueue<Runnable> queue) {
super(
corePoolSize,
maximumPoolSize,
keepAliveTimeInMs,
TimeUnit.MILLISECONDS,
queue,
new DefaultThreadFactory(name, uncaughtThrowableStrategy, preventNetworkOperations));
this.executeSynchronously = executeSynchronously;
}
/**
* Determines the number of cores available on the device.
*
* <p>{@link Runtime#availableProcessors()} returns the number of awake cores, which may not
* be the number of available cores depending on the device's current state. See
* http://goo.gl/8H670N.
*/
public static int calculateBestThreadCount() {
// We override the current ThreadPolicy to allow disk reads.
// This shouldn't actually do disk-IO and accesses a device file.
// See: https://github.com/bumptech/glide/issues/1170
ThreadPolicy originalPolicy = StrictMode.allowThreadDiskReads();
File[] cpus = null;
try {
File cpuInfo = new File(CPU_LOCATION);
final Pattern cpuNamePattern = Pattern.compile(CPU_NAME_REGEX);
cpus = cpuInfo.listFiles(new FilenameFilter() {
@Override
public boolean accept(File file, String s) {
return cpuNamePattern.matcher(s).matches();
}
});
} catch (Throwable t) {
if (Log.isLoggable(TAG, Log.ERROR)) {
Log.e(TAG, "Failed to calculate accurate cpu count", t);
}
} finally {
StrictMode.setThreadPolicy(originalPolicy);
}
int cpuCount = cpus != null ? cpus.length : 0;
int availableProcessors = Math.max(1, Runtime.getRuntime().availableProcessors());
return Math.min(MAXIMUM_AUTOMATIC_THREAD_COUNT, Math.max(availableProcessors, cpuCount));
}
GlideExecutor的newSourceExecutor與Executors的newFixedThreadPool類似,都是固定大小的線程池强衡,不過任務(wù)隊(duì)列不同擦秽。線程池大小為calculateBestThreadCount,該值為設(shè)備可用核心數(shù)但最大不超過4漩勤。任務(wù)隊(duì)列為PriorityBlockingQueue感挥,一種基于優(yōu)先級的無界阻塞隊(duì)列,插入元素需要實(shí)現(xiàn)Comparable接口的compareTo方法來提供排序依據(jù)越败。
*DecodeJob:
@Override
public int compareTo(DecodeJob<?> other) {
int result = getPriority() - other.getPriority();
if (result == 0) {
result = order - other.order;
}
return result;
}
Glide的Runnable實(shí)現(xiàn)類是DecodeJob触幼,它的compareTo方法的邏輯是:優(yōu)先級(共IMMEDIATE/HIGH/NORMAL/LOW四種,依次降低究飞,默認(rèn)為NORMAL)高的優(yōu)先置谦,若優(yōu)先級相同則順序在前的優(yōu)先(先進(jìn)先出)。
*RequestBuilder:
/**
* Set the target the resource will be loaded into.
*
* @param target The target to load the resource into.
* @return The given target.
* @see RequestManager#clear(Target)
*/
public <Y extends Target<TranscodeType>> Y into(@NonNull Y target) {
Util.assertMainThread();
Preconditions.checkNotNull(target);
if (!isModelSet) {
throw new IllegalArgumentException("You must call #load() before calling #into()");
}
Request previous = target.getRequest();
if (previous != null) {
requestManager.clear(target);
}
requestOptions.lock();
Request request = buildRequest(target);
target.setRequest(request);
requestManager.track(target, request);
return target;
}
*HttpUrlFetcher:
@Override
public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
long startTime = LogTime.getLogTime();
final InputStream result;
try {
result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
glideUrl.getHeaders());
} catch (IOException e) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Failed to load data for url", e);
}
callback.onLoadFailed(e);
return;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
+ " ms and loaded " + result);
}
callback.onDataReady(result);
}
private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
Map<String, String> headers) throws IOException {
if (redirects >= MAXIMUM_REDIRECTS) {
throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
} else {
// Comparing the URLs using .equals performs additional network I/O and is generally broken.
// See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
try {
if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
throw new HttpException("In re-direct loop");
}
} catch (URISyntaxException e) {
// Do nothing, this is best effort.
}
}
urlConnection = connectionFactory.build(url);
for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
}
urlConnection.setConnectTimeout(timeout);
urlConnection.setReadTimeout(timeout);
urlConnection.setUseCaches(false);
urlConnection.setDoInput(true);
// Stop the urlConnection instance of HttpUrlConnection from following redirects so that
// redirects will be handled by recursive calls to this method, loadDataWithRedirects.
urlConnection.setInstanceFollowRedirects(false);
// Connect explicitly to avoid errors in decoders if connection fails.
urlConnection.connect();
if (isCancelled) {
return null;
}
final int statusCode = urlConnection.getResponseCode();
if (statusCode / 100 == 2) {
return getStreamForSuccessfulRequest(urlConnection);
} else if (statusCode / 100 == 3) {
String redirectUrlString = urlConnection.getHeaderField("Location");
if (TextUtils.isEmpty(redirectUrlString)) {
throw new HttpException("Received empty or null redirect url");
}
URL redirectUrl = new URL(url, redirectUrlString);
return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
} else if (statusCode == -1) {
throw new HttpException(statusCode);
} else {
throw new HttpException(urlConnection.getResponseMessage(), statusCode);
}
}
@Override
public void cancel() {
// TODO: we should consider disconnecting the url connection here, but we can't do so
// directly because cancel is often called on the main thread.
isCancelled = true;
}
Glide將加載請求和Target(ImageView)關(guān)聯(lián)亿傅,開始某個(gè)ImageView的加載請求前會(huì)先將該ImageView關(guān)聯(lián)的請求清除媒峡。此時(shí)在線程池中的關(guān)聯(lián)的DecodeJob,正在進(jìn)行的網(wǎng)絡(luò)請求不會(huì)被中斷葵擎,在等待隊(duì)列里的也不會(huì)被直接從線程池移除谅阿,而是移除回調(diào)并設(shè)置取消標(biāo)志位,讓未開始的后續(xù)加載步驟的邏輯不會(huì)被執(zhí)行。
當(dāng)列表(ListView/RecyclerView)快速滾動(dòng)時(shí)签餐,同時(shí)執(zhí)行的網(wǎng)絡(luò)請求數(shù)量不會(huì)超過設(shè)備可用核心數(shù)寓涨,其余請求會(huì)放到隊(duì)列中等待執(zhí)行。雖然隊(duì)列長度可能會(huì)一下增加到幾十氯檐,但隨著列表復(fù)用View缅茉,隊(duì)列中的大部分請求都會(huì)被取消掉,之后執(zhí)行時(shí)不會(huì)發(fā)起網(wǎng)絡(luò)請求男摧,并迅速讓位于等待中的請求蔬墩。也就是說,快速滾動(dòng)過程的中間很多個(gè)列表項(xiàng)的請求都會(huì)被略過耗拓。這樣的機(jī)制保證了不會(huì)過度消耗資源導(dǎo)致滑動(dòng)卡頓拇颅。