HttpClient在多线程环境下踩坑总结
问题现场
在多线程环境下使用HttpClient组件对某个HTTP服务发起请求,运行一段时间之后发现客户端主机CPU利用率呈现出下降趋势,而不是一个稳定的状态。
而且,从程序日志中判断有线程处于夯住的状态,应该是被阻塞了。
CPU使用率逐步下降

问题排查
一开始找不到原因,怀疑是多线程并发导致的死锁问题,但是通过代码审查并未定位到任何可能的多线程并发问题。
甚至开始怀疑是否是因为内存资源不够引起JVM频繁GC到导致业务线程被暂停,但是从GC的日志输出结果看,GC是正常的。
于是,进入一种丈二和尚摸不着头脑头脑的状态,再次Review代码,发现并未设置请求超时时间,于是设置超时控制,发现问题依然存在,彻底懵逼了。
最后,dump线程堆栈和内存堆栈,再对堆栈数据进行分析。从分析结果看,确认是因为Socket连接在读取数据时被阻塞引起线程夯住。搜索“httpclient 超时”关键字,找到各式各样设置HttpClient超时控制的方式,均尝试过但是并未生效。
实际上到后来才知道,HttpCient的超时控制在不同的版本中设置请求超时参数的方式均各不相同,这才导致了我使用了网上看到的方式设置之后并未生效。当然,根本原因还是因为对HttpClient这个组件不熟悉导致的.

问题重现
1.HttpClient版本

<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>

2.Java代码

public class HttpClientTest {

private AtomicInteger counter = new AtomicInteger(0);
private String url = "http://www.baidu.com/";public static void main(String[] args) {new HttpClientTest().test();
}// 执行测试
private void test() {int number = 100000; // 总请求数int concurrent = 50; // 每次并发请求数CountDownLatch countDownLatch = new CountDownLatch(number); // 计数器ExecutorService threadPool = Executors.newFixedThreadPool(concurrent); // 线程池int concurrentPer = concurrent;boolean over = false;while(!over) {number = number - concurrent;if(number <= 0) {concurrentPer = number + concurrent;over = true;}// 线程池批量提交for(int i = 0; i < concurrentPer; i++) {threadPool.execute(new Runnable() {@Overridepublic void run() {try {request(url);Thread.sleep(100);} catch (IOException | InterruptedException e) {e.printStackTrace();} finally {countDownLatch.countDown();}}});}}try {countDownLatch.await();threadPool.shutdown();} catch (InterruptedException e) {e.printStackTrace();}
}// 访问指定地址
private void request(String url) throws IOException {HttpGet httpGet = new HttpGet(url);commnicate(httpGet);
}// 负责底层通信处理
private void commnicate(HttpRequestBase request) throws IOException {ResponseHandler<String> responseHandler = new ResponseHandler<String>() {@Overridepublic String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {return EntityUtils.toString(response.getEntity());}};HttpClient client = HttpClients.createDefault();String body = client.execute(request, responseHandler); // 线程可能会在这里被阻塞System.out.println(String.format("body size: %s, counter: %s", body.length(), counter.incrementAndGet()));
}

}
运行上述代码一段时间后很容易可以重现出问题,如下为运行控制台信息:
HttpClient重现报错

并且线程全部夯住,进程无法正常结束.

查看端口状态存在大量请求处于建立连接状态(ESTABLISHED):

netstat -anpt

tcp 0 0 172.17.7.81:56408 180.118.128.179:56033 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:57644 115.202.238.177:27016 ESTABLISHED 3847/java
tcp 0 0 172.17.7.81:36616 117.57.21.0:25719 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:59944 112.245.197.118:57220 ESTABLISHED 3847/java
tcp 0 0 172.17.7.81:48722 218.5.215.10:40835 ESTABLISHED 4007/java
tcp 0 0 172.17.7.81:52734 115.194.17.14:45210 ESTABLISHED 4007/java
tcp 0 0 172.17.7.81:60586 59.32.37.129:16637 ESTABLISHED 3686/java
tcp 0 0 172.17.7.81:36776 222.89.86.109:21667 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:51690 60.161.249.162:59039 ESTABLISHED 3927/java
tcp 0 0 172.17.7.81:42226 58.218.200.59:80 TIME_WAIT -
tcp 0 0 172.17.7.81:56566 117.70.47.194:40879 ESTABLISHED 3686/java
tcp 0 0 172.17.7.81:43266 182.120.202.204:45893 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:55630 60.169.223.16:21280 ESTABLISHED 3927/java
tcp 0 0 172.17.7.81:54922 60.168.81.26:25464 ESTABLISHED 3927/java
tcp 0 0 172.17.7.81:53352 112.252.97.83:53584 ESTABLISHED 3847/java
tcp 0 0 172.17.7.81:52684 113.121.242.43:14447 ESTABLISHED 3927/java
tcp 0 0 172.17.7.81:54750 113.121.241.168:45173 ESTABLISHED 3686/java
tcp 0 0 172.17.7.81:41510 113.105.202.106:47288 ESTABLISHED 4007/java
tcp 0 0 172.17.7.81:38804 121.232.148.62:57938 ESTABLISHED 3847/java
tcp 0 0 172.17.7.81:41468 113.105.202.106:47288 ESTABLISHED 3927/java
tcp 0 0 172.17.7.81:45444 123.163.81.185:22012 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:54810 113.121.241.168:45173 ESTABLISHED 4007/java
tcp 0 0 172.17.7.81:51542 175.153.23.147:20766 ESTABLISHED 3927/java
tcp 0 0 172.17.7.81:45644 218.5.215.10:40835 ESTABLISHED 4007/java
tcp 0 0 172.17.7.81:35730 116.53.197.198:30042 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:54738 113.121.241.168:45173 ESTABLISHED 3686/java
tcp 0 0 172.17.7.81:60600 59.32.37.129:16637 ESTABLISHED 3686/java
tcp 0 0 172.17.7.81:54862 113.121.241.168:45173 ESTABLISHED 4007/java
tcp 0 0 172.17.7.81:40980 115.225.153.215:17292 ESTABLISHED 3686/java
tcp 0 0 172.17.7.81:54166 123.149.162.129:18269 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:60712 120.35.190.184:33054 ESTABLISHED 3766/java
tcp 0 0 172.17.7.81:55802 106.42.211.65:59547 ESTABLISHED 3766/java
同时,分析线程堆栈信息(jstack -F -l pid > thread_stack.log)可以看到如下信息:

"pool-1-thread-45" #55 prio=5 os_prio=0 tid=0x00007f78702df000 nid=0x33d5 runnable [0x00007f7830c1d000]
java.lang.Thread.State: RUNNABLE

at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at org.apache.http.impl.io.SessionInputBufferImpl.streamRead(SessionInputBufferImpl.java:139)
at org.apache.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:155)
at org.apache.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:284)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:140)
at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:57)
at org.apache.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:261)
at org.apache.http.impl.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:165)
at org.apache.http.impl.conn.CPoolProxy.receiveResponseHeader(CPoolProxy.java:167)
at org.apache.http.protocol.HttpRequestExecutor.doReceiveResponse(HttpRequestExecutor.java:272)
at org.apache.http.protocol.HttpRequestExecutor.execute(HttpRequestExecutor.java:124)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:271)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:71)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:220)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:164)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:139)
at org.chench.extra.HttpClientTest.commnicate(HttpClientTest.java:106) # 线程在这里阻塞
at org.chench.extra.HttpClientTest.request(HttpClientTest.java:93)
at org.chench.extra.HttpClientTest.access$100(HttpClientTest.java:31)
at org.chench.extra.HttpClientTest$1.run(HttpClientTest.java:62)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)

Locked ownable synchronizers:

- <0x0000000086d50638> (a java.util.concurrent.ThreadPoolExecutor$Worker)

从堆栈日志中可以看到,线程处于RUNNABLE状态,并且阻塞在at org.chench.extra.HttpClientTest.commnicate(HttpClientTest.java:106)处.

解决方案
通过线程堆栈日志分析可以定位到线程夯住是因为HttpClient在执行访问时被阻塞了,结合源代码找到阻塞原因是因为未设置请求超时时间.

上述问题本质上是因为HttpClient组件并未设置请求超时控制导致的:虽然连接超时,但是读取失败,导致线程一直被阻塞.
那么,应该如何设置HttpClient的超时时间呢?鉴于HttpClient的官方文档没有明确说明,并且不同版本的HttpClient组件设置超时控制的方式不一致,所以建议直接查看源码.
HttpClient执行访问请求时序图如下:
HttpClient访问请求时序图

顺藤摸瓜,在MainClientExec.java的execute()方法中看到有2处使用了timeout参数,其含义各不相同:
(1)在获取HttpClientConnection对象时需要读取配置参数中的ConnectionRequestTimeout值,该参数用于控制获取连接的超时时间.
timeout参数
(2)获取到HttpClientConnection对象之后读取配置参数中的SocketTimeout值,设置Socket超时时间.
设置Socket超时
显然,这2个timeout参数都需要从RequestConfig对象中获取.
既然找到了使用timeout参数的地方,下一步需要确定该参数是如何设置的.沿着HttpClient的请求时序图路径往回查找,在InternalHttpClient.java类的doExecute()方法中可以很清晰地看到设置了RequestConfig对象参数.

@Override

protected CloseableHttpResponse doExecute(final HttpHost target,final HttpRequest request,final HttpContext context) throws IOException, ClientProtocolException {Args.notNull(request, "HTTP request");HttpExecutionAware execAware = null;if (request instanceof HttpExecutionAware) {execAware = (HttpExecutionAware) request;}try {final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request, target);final HttpClientContext localcontext = HttpClientContext.adapt(context != null ? context : new BasicHttpContext());// 通过RequestConfig对象配置连接参数RequestConfig config = null; if (request instanceof Configurable) {// 如果在HttpRequest对象中设置了RequestConfig属性,直接使用config = ((Configurable) request).getConfig(); }if (config == null) {// 如果在HttpRequest对象中未设置RequestConfig对象属性,则获取HttpParams属性构造RequestConfig对象final HttpParams params = request.getParams(); if (params instanceof HttpParamsNames) {if (!((HttpParamsNames) params).getNames().isEmpty()) {config = HttpClientParamConfig.getRequestConfig(params);}} else {config = HttpClientParamConfig.getRequestConfig(params);}}if (config != null) {// 使用RequestConfig对象配置连接参数localcontext.setRequestConfig(config); }setupContext(localcontext);final HttpRoute route = determineRoute(target, wrapper, localcontext);return this.execChain.execute(route, wrapper, localcontext, execAware);} catch (final HttpException httpException) {throw new ClientProtocolException(httpException);}
}

(3)HttpClient默认使用的连接池为PoolingHttpClientConnectionManager,在建立连接时(connect()方法)会使用其中的SocketConfig配置参数对Socket进行配置,如下所示:

PoolingHttpClientConnectionManager.java
@Override
public void connect(

    final HttpClientConnection managedConn,final HttpRoute route,final int connectTimeout,final HttpContext context) throws IOException {
Args.notNull(managedConn, "Managed Connection");
Args.notNull(route, "HTTP route");
final ManagedHttpClientConnection conn;
synchronized (managedConn) {final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);conn = entry.getConnection();
}
final HttpHost host;
if (route.getProxyHost() != null) {host = route.getProxyHost();
} else {host = route.getTargetHost();
}
final InetSocketAddress localAddress = route.getLocalSocketAddress();
SocketConfig socketConfig = this.configData.getSocketConfig(host);
if (socketConfig == null) {// 使用配置参数SocketConfigsocketConfig = this.configData.getDefaultSocketConfig();
}
if (socketConfig == null) {socketConfig = SocketConfig.DEFAULT;
}
this.connectionOperator.connect(conn, host, localAddress, connectTimeout, socketConfig, context);

}
DefaultHttpClientConnectionOperator.java
@Override

public void connect(final ManagedHttpClientConnection conn,final HttpHost host,final InetSocketAddress localAddress,final int connectTimeout,final SocketConfig socketConfig,final HttpContext context) throws IOException {final Lookup<ConnectionSocketFactory> registry = getSocketFactoryRegistry(context);final ConnectionSocketFactory sf = registry.lookup(host.getSchemeName());if (sf == null) {throw new UnsupportedSchemeException(host.getSchemeName() +" protocol is not supported");}final InetAddress[] addresses = host.getAddress() != null ?new InetAddress[] { host.getAddress() } : this.dnsResolver.resolve(host.getHostName());final int port = this.schemePortResolver.resolve(host);for (int i = 0; i < addresses.length; i++) {final InetAddress address = addresses[i];final boolean last = i == addresses.length - 1;Socket sock = sf.createSocket(context);// 使用socketConfig参数中的超时时间对Socket进行配置sock.setSoTimeout(socketConfig.getSoTimeout());sock.setReuseAddress(socketConfig.isSoReuseAddress());sock.setTcpNoDelay(socketConfig.isTcpNoDelay());sock.setKeepAlive(socketConfig.isSoKeepAlive());if (socketConfig.getRcvBufSize() > 0) {sock.setReceiveBufferSize(socketConfig.getRcvBufSize());}if (socketConfig.getSndBufSize() > 0) {sock.setSendBufferSize(socketConfig.getSndBufSize());}final int linger = socketConfig.getSoLinger();if (linger >= 0) {sock.setSoLinger(true, linger);}conn.bind(sock);// ...}
}

经过源码解读可以很明确地知道,在HttpClient 4.5.2版本中,设置连接参数有3种方式:
(1)在HttpRequest对象中设置RequestConfig对象属性
(2)在HttpRequest对象中设置HttpParams对象属性.
(3)在连接池对象中设置SocketConfig对象属性

既然找到了根源,下面分别通过这3种方式设置超时参数进行验证.
方式1: 通过RequestConfig对象设置超时参数

int timeOut = 5000;
RequestConfig requestConfig = RequestConfig.custom()

    .setConnectionRequestTimeout(timeOut) // 获取连接超时时间.setConnectTimeout(timeOut) // 设置HTTP连接超时时间.setSocketTimeout(timeOut) // 设置Socket超时时间.build();

request.setConfig(requestConfig);
方式2: 通过HttpParams对象设置超时参数

int timeOut = 5000;
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.SO_TIMEOUT, timeOut); // 设置Socket超时时间
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeOut); // 设置HTTP连接超时时间
request.setParams(params);
方式3: 通过连接池对象设置超时参数

int timeOut = 5000;
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
// 对连接池设置SocketConfig对象
connManager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeOut).build());
client = HttpClients.custom().setConnectionManager(connManager).build();
通过上述3种方式分别设置超时参数验证,虽然在运行过程中会有报错,但是不会导致线程被阻塞,进程能正常运行结束:
有报错但是不会出现线程夯住

解决问题之后客户端CPU使用率恢复正常:
CPU使用率基本稳定

总结/教训/反思
之所以会遇到这样的问题,还是因为对HttpClient组件不熟悉导致的;另外,在发现问题之后如何快速定位问题,并搜索稳定的解决方案很重要。
HttpClient组件每个版本的API变化都比较大,在使用时一定要彻底清楚当前使用的版本是如何设置超时时间的。而如何确定知道超时时间控制,通过源代码查看最为妥当.
在Java平台使用Http客户端组件,可以有多个选择:
(1)直接使用JDK提供的URL类访问
(2)使用HttpClient组件,有坑,不同版本设置参数的方式变动较大,最好是阅读一下当前使用版本的源码实现,正确设置好超时时间等参数
(3)如果使用了Spring MVC框架,还可以使用Spring MVC提供的RestTemplate组件,底层是使用Netty实现的客户端

遇到的这个坑本身并不属于技术难点,但是面对这个问题的解决思路值得总结:
(1)程序日志,运行日志非常关键,是定位问题时第一时间需要查看的
(2)代码review,逐行逐行地审查,首先排除可能存在的代码逻辑问题,比如:死锁等
(3)通过jstack命令查看线程堆栈信息: jstack -l -F > stack.log
(4)通过jmap命令查看内存堆栈信息: jmap -dump:live format=b,file=heap.bin
(5)如果结合搜索引擎和上述排查步骤依然未能解决问题,应该第一时间考虑直接阅读组件的源代码实现,特别是使用了开源组件时这可能才是真正解决问题的最佳路径

【参考】
https://blog.csdn.net/u011191463/article/details/78664896 HttpClient超时设置详解
https://my.oschina.net/jywm/blog/1834702 解决httpclient超时设置不生效的问题
https://www.jianshu.com/p/4b3e172c4f2d HttpClient 4.5.2-(四)连接超时的配置
https://www.jianshu.com/p/6a41c95855e3 HttpClient 4.5.2-(五)连接池的配置
https://www.jianshu.com/p/c852cbcf3d68 HttpClient高并发下性能优化-http连接池
https://blog.csdn.net/u011402596/article/details/44619443 HttpClient 多线程处理
https://field-notes.iteye.com/blog/2383759 多线程消费使用HttpClient引发的坑
http://blog.51cto.com/lihao001/1788490 httpclient4.3 导致线程阻塞
https://study121007.iteye.com/blog/2304274 HttpClient4.5.2 连接管理
https://www.jianshu.com/p/c852cbcf3d68 HttpClient高并发下性能优化-http连接池
https://alafqq.iteye.com/blog/2325041 httpclient 多线程执行(网上版本太多了。。。误人子弟)
https://gaozzsoft.iteye.com/blog/2352241 HttpClient 4.5.2版本设置连接超时时间-CloseableHttpClient设置Timeout
https://www.cnblogs.com/softidea/p/6964347.html HttpClient 专题
https://blog.csdn.net/Fhaohaizi/article/details/78217903 httpclient4.5如何确保资源释放
https://blog.csdn.net/u010634066/article/details/83120122 一场HttpClient调用未关闭流引发的问题
https://www.cnblogs.com/mumuxinfei/p/5066633.html Apache HttpClient使用之阻塞陷阱
https://issues.apache.org/jira/browse/HTTPCLIENT-1584 CloseableHttpClient - SSL Handshake has no Socket Timeout
https://monkeyissexy.github.io/2016/11/11/httpclient_ssl_handshake_socketTimeout_bug/ httpclient ssl handshake socketTimeout bug 分析解决过程
http://geekerwang.com/2017/10/22/%E8%AE%B0%E5%BD%95%E4%B8%80%E6%AC%A1%E5%9D%91%EF%BC%9AsocketRead-hang/ 记录一次坑:socketRead hang
https://www.cnblogs.com/jessezeng/p/7448636.html 解决: httpclient ssl 验证导致死锁问题
http://itbang.me/solu/detail/201 JAVA线程卡死问题如何定位?

作者:2Simple
出处:http://www.cnblogs.com/nuccch/

HttpClient在多线程环境下踩坑总结相关推荐

  1. VS Code Python 代码智能提示(自动补全)编译环境设置踩坑记录

    VS Code Python 编译环境设置踩坑记录 本菜鸡非常喜欢VS code的简洁风格,而且跟原来再用的VS2017相比简直不要太快,前段时间电脑重装之后又要在windows系统上重新搭建整套环境 ...

  2. VScode 乱装插件环境破坏踩坑自我反思总结

    VScode 乱装插件环境破坏踩坑自我反思总结 1.2021-01-18早上由于百度 <Vscode 常用插件推荐>,瞎装了插件,把自己的Vscode环境给破坏了 2.在文件-首选项-设置 ...

  3. 多线程环境下的线程不安全问题(1)

    在不考虑多线程的情况下,很多类代码都是完全正确的,但是如果放在多线程环境下,这些代码就很容易出错,我们称这些类为 线程不安全类 .多线程环境下使用线程安全类 才是安全的. 下面是一个线程不安全类的例子 ...

  4. Java多线程之单例模式在多线程环境下的安全问题

    Java多线程之单例模式在多线程环境下的安全问题 目录: 单例模式基本概念 单线程下的单例模式 多线程下的单例模式 单例模式volatile分析 1. 单例模式基本概念 基本概念转载自:单例模式|菜鸟 ...

  5. 多线程环境下,程序真是危机四伏

    姿势在不断的更新迭代, 太卷了. 你管这也叫线程安全? 最近大意了,竟然想将<面试官:实现一个带值变更通知能力的Dictionary>一文中的临界锁只应用到写操作. 内心旁白:读操作又不会 ...

  6. 单例模式的5种实现方式,以及在多线程环境下5种创建单例模式的效率

    这段时间从头温习设计模式.记载下来,以便自己复习,也分享给大家. [java] view plaincopy package com.iter.devbox.singleton; /** * 饿汉式 ...

  7. tensorflow-gpu_tensorflow GPU环境安装踩坑日记

    前言: 最近做一个TensorFlow的开源项目,用CPU跑的话,要消耗太多的时间,于是有了这篇配置GPU环境的踩坑日志 分享一些注意的地方 去官网查看经过测试的构建配置 我使用的配置是 Win10+ ...

  8. flask keras 多线程环境下加载模型

    keras 多线程环境下加载模型 Tensor Tensor is not an element of this graph. 问题场景 keras 使用flask 发布深度学习模型服务,模型有一个定 ...

  9. 多线程环境下HashMap导致CPU100%

    引言 昨天早上线上系统开始作业了一段时间以后,突然收到服务器报警,服务器CPU持续占用100%,导致线上系统不能正常使用,我登录服务器top了一下,发现java进程占用cpu400%, 由于前天晚上上 ...

最新文章

  1. poj 1753 Flip Game dfs 技巧
  2. [转]十天学习PHP之第一天(PHP)----基础知识
  3. python中列表 元组 字典 集合的区别
  4. 《大数据》2021年第4期目次摘要
  5. Java Web学习总结(16)——JSP的九个内置对象
  6. 2017-2018-2 20179215《密码与安全新技术》第七周作业
  7. 安装PaddleOCR遇到ERROR: Command errored out with exit status 1:command: ‘f:\python3.7\python.exe‘ -u -c
  8. 中国移动下一代移动技术将选择LTE
  9. ajax的理解与工作流程
  10. axios的this指向_vue使用axios时this指向哪里
  11. 三态门三个状态vhdl_人防门施工方案
  12. 如何判断电脑已感染“磁碟机”病毒?
  13. java数组的结构_详解Java数组结构
  14. js ajax 401,$ .ajax请求总是401(UNAUTHORIZED)
  15. 软件工程大作业(1)
  16. 基于JavaEE的医院网上预约挂号系统
  17. Nginx-webpy快速搭建反向代理服务及web服务
  18. 如何在移动钱包中搭建一个小程序应用商店
  19. 将MindManager添加到鼠标右键新建项
  20. 不再封控,各高校要如何开展教学

热门文章

  1. 2021年秋招简历:张宁宁-硕士-苏州大学-信息与通信工程
  2. 从几个方面制作网站seo优化整体方案
  3. 汽车常识全面介绍 - 引擎详论
  4. dhu复试基础——36 水果价格
  5. 图片制作二次元头像,表情很到位哦
  6. 幼师和计算机学哪个好,大学不容易脱单的专业,计算机专业只是其一,幼师也会比较难...
  7. java 类作为参数_如何将类类型作为函数参数传递
  8. 左手力右手电,右手还定磁感线
  9. java 布尔值变成字符串,Java将布尔值转换为字符串
  10. Ubuntu 16.04 解决钉钉、微信等打开chrome时无法打开链接,只能停留在主页的问题