前言
在做业务功能的时候,难免会遇到一个功能需要几部分的信息,结果获取完毕后,然后把它们聚合起来构造对应的响应信息,通常情况下我们会把这些毫无相关的业务处理串行的处理掉,这种处理方式当然可以解决这个业务。但是考虑到接口响应时间相关的问题时,或许我们可以使用其它办法来提高它的响应速度。
问题:串行处理
串行,顾名思义就是从上到下依次处理,各处理时间累加,从而导致了响应过慢等问题的出现。
public class SerialDemo {
@Data
static class Result {
private String value1;
private String value2;
}
public static void main(String[] args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Result result = new Result();
try {
Thread.sleep(1000);
result.setValue1("get t1 result");
} catch (InterruptedException ignored) {
}
try {
Thread.sleep(1000);
result.setValue2("get t2 result");
} catch (InterruptedException ignored) {
}
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
System.out.println(result);
}
}
2018
SerialDemo.Result(value1=get t1 result, value2=get t2 result)
毫无意外的,处理时间大于2000ms+
,接下来我们尝试使用join()
解决该问题。
1.使用join()解决该问题
主线程创建并启动子线程,如果子线程需要进行大量的耗时运算,主线程往往将早于子线程结束之前结束,这时,如果主线程想要等待子线程执行完成之后再结束,就要用到join()
了。join()
的作用是等待线程对象销毁,关于join()
想要进一步了解其原理的可以看看另一篇文章的对应小节。
public class JoinDemo {
@Data
static class Result {
private String value1;
private String value2;
}
public static void main(String[] args) throws InterruptedException {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Result result = new Result();
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
result.setValue1("get t1 result");
} catch (InterruptedException ignored) {
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
result.setValue2("get t2 result");
} catch (InterruptedException ignored) {
}
});
t1.start();
t2.start();
t1.join();
t2.join();
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
System.out.println(result);
}
}
1028
JoinDemo.Result(value1=get t1 result, value2=get t2 result)
处理耗时1028ms
,相比较串行的处理方式,耗时大量减少!
join()
让当前线程等待join()的线程执行结束。原理是循环检查join()的线程是否存活,如果join()的线程存活则一直等待,当线程执行完毕后,调用this.notifyAll()
进入后续流程。
// java.lang.Thread#join(long)
// millis默认为0
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
除了这种方式还有没有其它办法呢 ?
2.使用CountDownLatch解决该问题
CountDownLatch
允许一个或多个线程等待其它线程完成操作。
public class CountDownLatchDemo {
@Data
static class Result {
private String value1;
private String value2;
}
public static void main(String[] args) throws InterruptedException {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
CountDownLatch countDownLatch = new CountDownLatch(2);
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Result result = new Result();
threadPool.submit(() -> {
try {
Thread.sleep(1000);
result.setValue1("get t1 result");
} catch (InterruptedException ignored) {
} finally {
countDownLatch.countDown();
}
});
threadPool.submit(() -> {
try {
Thread.sleep(1000);
result.setValue2("get t2 result");
} catch (InterruptedException ignored) {
} finally {
countDownLatch.countDown();
}
});
countDownLatch.await();
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
System.out.println(result);
}
}
1041
CountDownLatchDemo.Result(value1=get t1 result, value2=get t2 result)
同样的,相比较串行的处理方式,耗时大量减少!
CountDownLatch
使用给定的计数(N)进行初始化,当我们调用CountDownLatch
的countDown()
时,N = N - 1,CountDownLatch
的await()
会阻塞当前线程,直到N变成零。
3.使用CompletableFuture解决该问题
看名字我们就可以了解到,它是Future
的增强与扩展。可以显式完成的Future,支持在完成时触发的相关功能和操作。
public class CompletableFutureDemo {
@Data
static class Result {
private String value1;
private String value2;
}
public static void main(String[] args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ExecutorService threadPool = Executors.newFixedThreadPool(2);
Result result = new Result();
@SuppressWarnings("unchecked")
CompletableFuture<Void>[] completableFutures = new CompletableFuture[]{CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
result.setValue1("get t1 result");
} catch (InterruptedException ignored) {
}
}, threadPool), CompletableFuture.runAsync(() -> {
try {
Thread.sleep(1000);
result.setValue2("get t2 result");
} catch (InterruptedException ignored) {
}
}, threadPool)};
CompletableFuture.allOf(completableFutures).join();
System.out.println(result);
stopWatch.stop();
System.out.println(stopWatch.getTotalTimeMillis());
threadPool.shutdown();
}
}
1036
CompletableFutureDemo.Result(value1=get t1 result, value2=get t2 result)
CompletableFuture
涉及到的内容比较多,后续会单独写一篇关于它API的介绍与使用的博客。
总结
类似的处理方式还有很多,本文就不一一列举了,本文简单介绍了使用join()
、CountDownLatch
、CompletableFuture
的简单使用,本文介绍的比较粗浅,只是提供了一些优化思路,后续会针对CountDownLatch
和CompletableFuture
单独写一篇详细的博客,好了,到此为止。