okHttp介绍
okHttp源码地址
由Square公司贡献的一个处理网络请求的开源项目,是目前Android使用最广泛的网络框架。从4.4开始HttpURLConnection的底层实现采用的是okHttp。
okHttp优点
- 支持Spdy、Http1.X、Http2、Quic以及WebSocket
- 连接池复用底层TCP(Socket),减少请求延时
- 无缝的支持GZIP减少数据流量
- 缓存响应数据减少重复的网络请求
- 请求失败自动重试主机的其他ip,自动重定向
okHttp基础使用
Implementation "com.squareup.okhttp3:okhttp:$Version"
Q1:如何决定将请求放入ready还是running?
在Dispatcher类中
synchronized void enqueue(RealCall.AsyncCall call) {
//1.对正在请求的数量有限制64
// 2.同一域名正在请求的个数也是有限制的 默认是5个
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
Q2: 从ready移动running的条件是什么?
在RealCall类中
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
这里会执行到client.dispatcher().finished(this);
void finished(RealCall call) {
finished(runningSyncCalls, call, false);
}
private <T> void finished(Deque<T> calls, T call, boolean promoteCalls) {
int runningCallsCount;
Runnable idleCallback;
synchronized (this) {
if (!calls.remove(call)) throw new AssertionError("Call wasn't in-flight!");
if (promoteCalls) promoteCalls();
runningCallsCount = runningCallsCount();
idleCallback = this.idleCallback;
}
if (runningCallsCount == 0 && idleCallback != null) {
idleCallback.run();
}
}
runningAsyncCalls中 移除掉这次的call后,会执行promoteCalls()方法
private void promoteCalls() {
//再判断一次正在请求数限制与ready要有数据
if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
//遍历ready
for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {
AsyncCall call = i.next();
//如果拿到的等待请求的host在正在请求的列表中还没达到5个
if (runningCallsForHost(call) < maxRequestsPerHost) {
i.remove();
runningAsyncCalls.add(call);
executorService().execute(call);
}
if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
}
}
/** Returns the number of running calls that share a host with {@code call}. */
private int runningCallsForHost(AsyncCall call) {
int result = 0;
for (AsyncCall c : runningAsyncCalls) {
if (c.get().forWebSocket) continue;
if (c.host().equals(call.host())) result++;
}
return result;
}
Q3: 分发器线程池的工作行为?
okHttp默认线程池executorService在dispatcher类中做了一个初始化(自己新建一个也可以)
public synchronized ExecutorService executorService() {
if (executorService == null) {
//为啥核心线程数是0?是因为如果你再也不会用到网络请求 那这个核心线程存在没有什么意义(极端场景)
executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));
}
return executorService;
}
//线程池
//1.corePoolSize核心线程数
//2.maximumPoolSize最大线程数 这个数量是包括了核心线程数的数量 如果最大线程数跟核心线程数一样 说明只有核心线程
//3.keepAliveTime 最大线程数去掉核心线程数后剩余的线程(空闲线程)缓存的时间
//4.unit缓存时间的参数 一般是秒
//5.workQueue线程队列 使用的是BlockingQueue<Runnable> ,BlockingQueue<Runnable>是线程阻塞队列,后面有讲到这个地方有三种队列
//5.threadFactory线程工厂
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
//线程工厂 为了给线程指定一个name
public static ThreadFactory threadFactory(final String name, final boolean daemon) {
return new ThreadFactory() {
@Override public Thread newThread(Runnable runnable) {
Thread result = new Thread(runnable, name);
result.setDaemon(daemon);
return result;
}
};
}
延伸 为什么oktthp使用的是SynchronousQueue(是无容量队列) 而不是其他俩种?(此处我是反复看了几遍才理解的)
首先阐述下这三个队列的基本概念
假设向线程池提交任务时,核心线程都被占用的情况下:
ArrayBlockingQueue:基于数组的阻塞队列,初始化需要指定固定大小。
当使用此队列时,向线程池提交任务,会首先加入到等待队列中,当等待队列满了之后,再次提交任务,尝试加入队列就会失败,这时就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。所以最终可能出现后提交的任务先执行,而先提交的任务一直在等待。
LinkedBlockingQueue:基于链表实现的阻塞队列,初始化可以指定大小,也可以不指定。
当指定大小后,行为就和ArrayBlockingQueu一致。而如果未指定大小,则会使用默认的Integer.MAX_VALUE作为队列大小。这时候就会出现线程池的最大线程数参数无用,因为无论如何,向线程池提交任务加入等待队列都会成功。最终意味着所有任务都是在核心线程执行。如果核心线程一直被占,那就一直等待。
SynchronousQueue : 无容量的队列。
使用此队列意味着希望获得最大并发量。因为无论如何,向线程池提交任务,往队列提交任务都会失败。而失败后如果没有空闲的非核心线程,就会检查如果当前线程池中的线程数未达到最大线程,则会新建线程执行新提交的任务。完全没有任何等待,唯一制约它的就是最大线程数的个数。因此一般配合Integer.MAX_VALUE就实现了真正的无等待。
实战例子讲解分析
例子1
private void testThread() {
ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(1);
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS
, blockingQueue);
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务1:" + Thread.currentThread());
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务2:" + Thread.currentThread());
}
});
}
日志
2020-07-10 21:35:16.427 com.xm.wanapp D: 任务2:Thread[pool-5-thread-2,5,main]
2020-07-10 21:35:16.427 com.xm.wanapp D: 任务1:Thread[pool-5-thread-1,5,main]
2020-07-10 21:35:21.734 com.xm.wanapp D: 任务2:Thread[pool-6-thread-2,5,main]
2020-07-10 21:35:21.734 com.xm.wanapp D: 任务1:Thread[pool-6-thread-1,5,main]
2020-07-10 21:35:51.404 com.xm.wanapp D: 任务1:Thread[pool-7-thread-1,5,main]
2020-07-10 21:35:51.405 com.xm.wanapp D: 任务2:Thread[pool-7-thread-2,5,main]
代码分析
我们启动2个任务后 第一个任务会先进入队列中,然后立即执行了executor.execute方法
public void execute(Runnable command) {
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
会进入addWorker(null, false)方法,按理来说会创建线程先执行任务1,然后把任务2加入到队列中,此时不存在空闲线程且队列容量为1,所以会 等任务1执行完了再执行任务2,因为这个时候是处于上图中所说的当线程数大于核心线程数,不存在空闲线程,新任务会添加到等待队列。然而日志出现了先执行任务2,再执行任务1的场景(这点不太理解,如果后续找到原因再补充),总的来说线程1,线程2会执行。
例子2
private void testThread() {
ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(1);
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS
, blockingQueue);
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务1:" + Thread.currentThread());
//多了这一步
while (true){
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务2:" + Thread.currentThread());
}
});
}
日志
2020-07-10 21:55:30.042 com.xm.wanapp D: 任务1:Thread[pool-1-thread-1,5,main]
2020-07-10 21:55:30.042 com.xm.wanapp D: 任务2:Thread[pool-1-thread-2,5,main]
这次结果又超出我的预料,好尴尬,我的认知里按理来说线程1进入队列以后会直接执行,但是因为while为true导致,线程2会进入队列中等待空闲线程,但是现在没有空闲线程 那线程2会处于一直等待中,直到线程1执行完,所以按理来说日志应该只会打印线程1的日志,也留到下一次定位到问题再说
例子3
private void testThread() {
LinkedBlockingDeque<Runnable> blockingQueue = new LinkedBlockingDeque<>(1);
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS
, blockingQueue);
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务1:" + Thread.currentThread());
while (true){
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务2:" + Thread.currentThread());
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务3:" + Thread.currentThread());
}
});
}
日志
2020-07-10 21:59:01.927 ? D: 任务1:Thread[pool-1-thread-1,5,main]
2020-07-10 21:59:01.927 ? D: 任务2:Thread[pool-1-thread-2,5,main]
2020-07-10 21:59:01.927 ? D: 任务3:Thread[pool-1-thread-2,5,main]
此处跟上个例子不同的是使用的是LinkedBlockingDeque且容量设置为1,算了,我已经放弃挣扎了,看了这个日志,我已经无法诡辩了,这还是打印了线程2跟线程3的日志,并没有被线程1卡住,后续吧
例子4
private void testThread() {
LinkedBlockingDeque<Runnable> blockingQueue = new LinkedBlockingDeque<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS
, blockingQueue);
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务1:" + Thread.currentThread());
while (true){
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务2:" + Thread.currentThread());
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务3:" + Thread.currentThread());
}
});
}
日志
2020-07-10 22:04:37.139 com.xm.wanapp D: 任务1:Thread[pool-1-thread-1,5,main]
这次日志其实才符合我的预期,因为虽然我容量值为Integer.MAX_VALUE,但是因为没有空闲线程,所以只会执行线程1
例子5
private void testThread() {
SynchronousQueue<Runnable> blockingQueue = new SynchronousQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS
, blockingQueue);
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务1:" + Thread.currentThread());
while (true){
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务2:" + Thread.currentThread());
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务3:" + Thread.currentThread());
}
});
}
日志
2020-07-10 22:06:39.261 com.xm.wanapp D: 任务1:Thread[pool-1-thread-1,5,main]
2020-07-10 22:06:39.261 com.xm.wanapp D: 任务3:Thread[pool-1-thread-3,5,main]
2020-07-10 22:06:39.262 com.xm.wanapp D: 任务2:Thread[pool-1-thread-2,5,main]
这次其实才是我想要的,因为SynchronousQueue的容器默认为0且没有空余线程,所以线程2、3添加到任务队列会失败,又因为线程数量小于最大线程数,因此会新建线程执行新任务,不会出现卡死的现象,这就是为啥使用SynchronousQueue
延伸例子6看看执行的先后顺序
private void testThread() {
ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(1);
ThreadPoolExecutor executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS
, blockingQueue);
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务1:" + Thread.currentThread());
try {
Thread.sleep(1_1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务2:" + Thread.currentThread());
}
});
executor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG,"任务3:" + Thread.currentThread());
}
});
}
日志
2020-07-10 22:12:55.803 com.xm.wanapp D: 任务1:Thread[pool-1-thread-1,5,main]
2020-07-10 22:12:55.808 com.xm.wanapp D: 任务3:Thread[pool-1-thread-3,5,main]
2020-07-10 22:12:55.820 com.xm.wanapp D: 任务2:Thread[pool-1-thread-2,5,main]
分析:执行顺序是1,3,2 部分符合预期 因为1先执行但是有个延时,导致2在队列中等待,当执行到任务3的时候添加到任务队列中失败,那就会新建线程2先执行任务3,等待任务3执行完了 任务1还在执行 那就会使用线程2执行任务2,按理来说只会有线程1跟2 但是实际日志中出现了任务3 不太理解
用到的知识:
1.双端队列