Jdk8 ParallelStream(并行流),自定义线程池,性能测试,okhttps同步及异步请求,为了使用上这个东西,昨晚研究了一番。我认为他只有在某些特殊场景下适用,如下:
来自OkHttps官网的简介及依赖说明
OkHttps 是 2020 年开源的对 OkHttp3 轻量封装的框架,它独创的异步预处理器,特色的标签,灵活的上传下载进度监听与过程控制功能,在轻松解决很多原本另人头疼问题的同时,设计上也力求纯粹与优雅。
OkHttps 默认依赖 OkHttp3 的 3.x 的最新 3.14.9 版本,但是已经全面兼容 OkHttp3 的 4.x 版本。默认依赖之所以不是 4.x,主要考虑到:
- OkHttp3 4.x 是用 Kotlin 重写,包体相对较大(约是 3.x 的 1.8 倍)
- 在没有 Kotlin 依赖的纯 Java 项目中,使用 4.x 会存在一些问题。
<dependency> <groupId>com.ejlchina</groupId> <artifactId>okhttps</artifactId> <version>3.5.3</version> </dependency>
package jdk8ParallelStreamTest;
import com.ejlchina.okhttps.HTTP;
import com.ejlchina.okhttps.HttpResult;
import okhttp3.ConnectionPool;
import okhttp3.Response;
import org.apache.commons.httpclient.HttpStatus;
import org.springframework.util.StopWatch;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class Jdk8ParallelStreamTest {
private static final DateTimeFormatter format = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd HH:mm:ss").toFormatter();
// 日期转换为字符串
public static String stringOfDate(Date date) {
if (date != null) {
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
return localDateTime.format(format);
} else {
return null;
}
}
static class UrlInfoEntity {
private String url;
private int state;
private Date warnTime;
private String warnMsg;
public UrlInfoEntity(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public Date getWarnTime() {
return warnTime;
}
public void setWarnTime(Date warnTime) {
this.warnTime = warnTime;
}
public String getWarnMsg() {
return warnMsg;
}
public void setWarnMsg(String warnMsg) {
this.warnMsg = warnMsg;
}
@Override
public String toString() {
return "{url:'" + url + '\'' +
", state:" + state +
", warnTime:" + stringOfDate(warnTime) +
", warnMsg:'" + warnMsg + '\'' +
"}";
}
}
HTTP http = HTTP.builder()
.config(b -> {
// 精细化配置
// 连接超时时间(默认10秒)
b.connectTimeout(10, TimeUnit.SECONDS);
// 写入超时时间(默认10秒)
b.writeTimeout(10, TimeUnit.SECONDS);
// 读取超时时间(默认10秒)
b.readTimeout(10, TimeUnit.SECONDS);
// 配置连接池 最小10个连接(不配置默认为 5)
b.connectionPool(new ConnectionPool(21, 7, TimeUnit.MINUTES));
// 拦截器
b.addInterceptor(chain -> {
int retryTimes = 1;
while (true) {
Response response = null;
try {
response = chain.proceed(chain.request());
if (response.code() != 200 && retryTimes < 4) {
log.warn(chain.request().url() + " 失败重试第" + retryTimes + "次!");
// 注意,这里一定要 close 掉失败的 Response
response.close();
retryTimes++;
continue;
}
return response;
} catch (Exception e) {
if (retryTimes < 4) {
log.warn(chain.request().url() + " 失败重试第" + retryTimes + "次!");
if (response != null) {
// 注意,这里一定要 close 掉失败的 Response
response.close();
}
retryTimes++;
} else {
if (response != null) {
// 注意,这里一定要 close 掉失败的 Response
response.close();
}
throw e;
}
} finally {
if (response != null) {
response.close();
}
}
}
});
}).build();
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 待处理数据
List<UrlInfoEntity> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
UrlInfoEntity urlMonitorEntity;
if (i % 5 == 0) {
urlMonitorEntity = new UrlInfoEntity("https://www.baidu.com999");
} else if (i % 5 == 1) {
urlMonitorEntity = new UrlInfoEntity("https://ok.zhxu.cn/v4/introduction.html");
} else if (i % 5 == 2) {
urlMonitorEntity = new UrlInfoEntity("https://developer.mozilla.org/zh-CN/docs/Web/CSS");
} else if (i % 5 == 3) {
urlMonitorEntity = new UrlInfoEntity("https://www.runoob.com/java/java-tutorial666.html");
} else {
urlMonitorEntity = new UrlInfoEntity("https://mvnrepository.com/");
}
list.add(urlMonitorEntity);
}
StopWatch stopWatch = new StopWatch("计秒表");
/* stopWatch.start("使用单线程耗时:");
test1(list);
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + stopWatch.getLastTaskTimeMillis() + "ms"); */
/* stopWatch.start("使用默认线程池耗时:");
test2(list);
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + stopWatch.getLastTaskTimeMillis() + "ms"); */
/* stopWatch.start("使用ForkJoinPool自定义线程池耗时:");
test3(list);
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + stopWatch.getLastTaskTimeMillis() + "ms"); */
/* stopWatch.start("使用ForkJoinPool自定义线程池及同步请求耗时:");
test5(list);
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + stopWatch.getLastTaskTimeMillis() + "ms"); */
stopWatch.start("使用ForkJoinPool自定义线程池及异步请求耗时:");
test6(list);
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + stopWatch.getLastTaskTimeMillis() + "ms");
/* stopWatch.start("使用单线程异步请求耗时:");
test7(list);
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + stopWatch.getLastTaskTimeMillis() + "ms"); */
}
// 使用单线程
private static void test1(List<UrlInfoEntity> list) {
list.forEach(urlInfo -> {
try {
// 模拟延时,省时不设置那么大
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("list1 size:" + list.size() + "\nlist1:" + list);
}
// 使用默认线程池
private static void test2(List<UrlInfoEntity> list) {
list.parallelStream().forEach(urlInfo -> {
try {
// 模拟延时,省时不设置那么大
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("list2 size:" + list.size() + "\nlist2:" + list);
}
// 使用ForkJoinPool自定义线程池
private static void test3(List<UrlInfoEntity> list) throws ExecutionException, InterruptedException {
ForkJoinPool customThreadPool = new ForkJoinPool(35);
List<UrlInfoEntity> list3 = customThreadPool.submit(() -> list.parallelStream().peek(urlInfo -> {
try {
// 模拟延时,省时不设置那么大
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).collect(Collectors.toList())).get();
System.out.println("list3 size:" + list3.size() + "\nlist3:" + list3);
}
// 使用ForkJoinPool自定义线程池,同步请求
private static void test5(List<UrlInfoEntity> list) throws ExecutionException, InterruptedException {
ForkJoinPool customThreadPool = new ForkJoinPool(35);
List<UrlInfoEntity> list5 = customThreadPool.submit(() -> list.parallelStream().peek(urlInfo -> {
// 同步,方法 nothrow() 让异常不直接抛出
String url = urlInfo.getUrl();
HttpResult result = http.sync(urlInfo.getUrl()).nothrow().get();
monitorAddress(urlInfo, result, url);
}).collect(Collectors.toList())).get();
System.out.println("list5 size:" + list5.size() + "\nlist5:" + list5);
}
// 使用ForkJoinPool自定义线程池,异步请求
private static void test6(List<UrlInfoEntity> list) throws ExecutionException, InterruptedException {
ForkJoinPool customThreadPool = new ForkJoinPool(35);
List<UrlInfoEntity> list6 = customThreadPool.submit(() -> list.parallelStream().peek(urlInfo -> {
// 异步,方法 nothrow() 让异常不直接抛出
String url = urlInfo.getUrl();
HttpResult result = http.async(url).nothrow().get().getResult();
monitorAddress(urlInfo, result, url);
}).collect(Collectors.toList())).get();
System.out.println("list6 size:" + list6.size() + "\nlist6:" + list6);
}
public static void monitorAddress(UrlInfoEntity urlInfo, HttpResult result, String url) {
if (result.getStatus() == HttpStatus.SC_OK) {
// 相当于continue终止的是这一轮的list遍历
return;
} else if (!"?wsdl".equals(url.substring(url.length() - 5)) && result.getStatus() == HttpStatus.SC_INTERNAL_SERVER_ERROR &&
result.getBody() != null && result.getBody().toString().contains("HTTP GET PATH_INFO")) {
// 对wsdl地址做兼容
return;
}
urlInfo.setWarnTime(Date.from(Instant.now()));
urlInfo.setState(result.getStatus());
// 判断执行状态
switch (result.getState()) {
case RESPONSED: // 请求已正常响应
try {
// 模拟延时,省时不设置那么大
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
urlInfo.setWarnMsg("请求已响应,但返回:" + HttpStatus.getStatusText(result.getStatus()));
break;
case CANCELED: // 请求已被取消
urlInfo.setWarnMsg("请求已被取消");
break;
case NETWORK_ERROR: // 网络错误,说明用户没网了
urlInfo.setWarnMsg("网络错误");
break;
case TIMEOUT: // 请求超时
urlInfo.setWarnMsg("请求超时");
break;
case EXCEPTION: // 其它异常
urlInfo.setWarnMsg("其它异常");
break;
}
// 还可以获得具体的异常信息
System.out.println(Thread.currentThread().getName() + " --> " + urlInfo + "<--异常:" + result.getError());
}
// 使用单线程异步请求耗时
private static void test7(List<UrlInfoEntity> list) {
list.forEach(urlInfo -> {
// 异步,方法 nothrow() 让异常不直接抛出
String url = urlInfo.getUrl();
HttpResult result = http.async(urlInfo.getUrl()).nothrow().get().getResult();
monitorAddress(urlInfo, result, url);
});
System.out.println("list7 size:" + list.size() + "\nlist7:" + list);
}
}
test方法要一个个跑啊,加上初始化需要的时间等,都是为了准确性。



这两个单独对比使用了http调用地址且有重试机制,异步请求按理来说是快更多的,主要这里没模拟更复杂的业务场景。

只要有重试机制在单线程异步请求是绝对更慢的,就不跟这两个比了,当然代码提供了。
转载请注明出处,原创方案技术。
433



被折叠的 条评论
为什么被折叠?



