文章目录
随机简历要求截图
就在网上随便找了几家招聘的,条件都是15-20K,放在武汉相当于中等偏上了,北上广深的就别看了,
出现的一个频率非常高的词汇,多线程、多线程编程、说说你对多线程的理解等等,其实我出去面试面试官一般都是会让你自己讲一些场景,然后根据场景讲讲遇到的问题,所以这个时候加入问到的场景涉及到多线程,如果知道多线程的一些技术以及场景,没做过,能说出来也是可以的。
所以,根据鄙人7年的编程经验,以及了解相关方面的知识,小弟不才,整理了部分多线程的代码实例以及框架,以及多线程的缺点等等。
多线程的依赖包,基于现在用户还是比较多的JDK8提供的API来写一些实例代码。
线程与多线程:
世间万物都可以同时完成很多工作。例如,人体可以同时进行呼吸、血液循环、思考问题等活动。用户既可以使用计算机听歌,也可以编写文档和发送邮件,而这些活动的完成可以同时进行。这种同时执行多个操作的“思想”在 [Java]中被称为并发,而将并发完成的每一件事称为线程。
在 Java 中,并发机制非常重要,但并不是所有程序语言都支持线程。在以往的程序中,多以一个任务完成以后再进行下一个任务的模式进行,这样下一个任务的开始必须等待前一个任务的结束。Java 语言提供了并发机制,允许开发人员在程序中执行多个线程,每个线程完成一个功能,并与其他线程并发执行。这种机制被称为多线程。
多线程的使用场景:
我们在工作中,各种相关服务、数据库、api的调用,有返回时间的差异,这个时候必须使用多线程了。
多线程的编码实践:
异步编程的难点:
- 如何优雅地实现异步编程一直都是一个难题,异步编程的通常做法就是采用callback的方法,但是这种方法通常会把代码嵌套在正常流程的代码中,而且当有多层嵌套的时候代码更加难以维护。
- 另外还有一点,异步编程的异常处理也是难以未维护,特别是在Java中,异步编程通常由新的线程完成,而子线程的异常是无法在父线程捕获的,那么对于异步执行结果的获取就需要付出更大的代价,比如通过:轮询、事件驱动等来完成。
从Future说起
Java5之后就引入Future用于异步编程,通过get()方法来对异步执行结果的同步等待和结果获取:
Future<String> doSomething = Executors.newSingleThreadExecutor().submit(() -> {
try {
Thread.sleep(1000 * 3);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "success";
});
String result = doSomething.get();
System.out.println(result);
Future的Api比较简单,而已对异常处理不友好,如果有同时有多个异步操作需要同时进行是就不好处理了
假设有这么一个场景,用户登录拿到登录凭证(token),登录之后获取用户信息。
ExecutorService executors = Executors.newFixedThreadPool(10);
Future<String> login = executors.submit(()->login());
String token = login.get();
Future<String> userInfo = executors.submit(() -> userInfo(token));
String userInfoResult = userInfo.get();
System.out.println(userInfoResult);
这种实现方法还是不能实现真正的异步编程或者说不是我们所期望的,我们期望的是登录后获取用户信息,但这两件事情完成后统一对结果进行处理,而这种方式是先等待登录之后再取用户信息,和同步调用类似,这就与我们的设想不符。
CompletableFuture
初识CompletableFuture
在Java8中引入了CompletableFuture类,同时实现了Future接口和CompletionStage接口,提供了一套用于异步编程的Api接口并且提供了异步处理
CompletableFuture提供了许多异步编程的操作,可以说是Java中.的Promise了,下面通过CompletableFuture来实现上面提到的例子:
String userInfo = CompletableFuture.supplyAsync(() -> login())
.thenApplyAsync(token -> userInfo(token))
.get();
System.out.println(userInfo);
CompletableFuture API
CompletableFuture方法很多,功能也很丰富,这里不一一说明,主要可以分为这几类来使用:
1.把CompletableFuture当Future使用
CompletableFuture实现了Future接口,也就是Future能做的CompletableFuture也同样能使用,加上complete和completeExceptionally方法可以控制结果的结束
CompletableFuture<String> f = new CompletableFuture<>();
Executors.newSingleThreadExecutor().submit(()->{
f.complete("hello");
//f.completeExceptionally(new RuntimeException("error"));
});
String result = f.get();
System.out.println(result);
可以通过CompletableFuture来控制多个异步操作同时执行:
CompletableFuture<String> f = new CompletableFuture<>();
new Thread(() -> {
try {
System.out.println("thread1:" + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("thread2:" + f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}).start();
f.complete("hello");
2.异步操作
创建异步操作的方法主要是:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
使用如下:
CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello";
});
String result = f.get();
System.out.println(result);
3.连续异步操作
public CompletableFuture<Void> thenRun(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action)
public CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor)
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor)
使用如下:
CompletableFuture<Void> f = CompletableFuture
.supplyAsync(() -> "hello")
.thenApplyAsync(res -> res + " world!")
.thenAcceptAsync(System.out::println);
// wait for job done
f.get();
4.等待操作完成
public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor)
使用如下:
CompletableFuture<String> f = CompletableFuture
.supplyAsync(() -> "hello")
.thenApplyAsync(res -> res + " world!")
.whenComplete((res, err) -> {
if (err != null) {
err.printStackTrace();
} else {
System.out.println(res);
}
});
// wait for job done
f.get();
5.组合
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,Executor executor)
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor)
使用如下:
CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(res -> CompletableFuture.supplyAsync(() -> res + " World,"))
.thenCombine(CompletableFuture.supplyAsync(() -> "CompletableFuture!"), (a, b) -> a + b);
String result = f.get();
System.out.println(result);//Hello World,CompletableFuture!
6.结果&异常处理
// 异常处理
CompletableFuture<Object> f = CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(res -> res + "World")
.thenApplyAsync(res -> {
throw new RuntimeException("error");
})
.exceptionally(e -> {
//handle exception here
e.printStackTrace();
return null;
});
f.get();
// 执行结果处理
CompletableFuture<Object> f2 = CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(res -> res + "World")
.thenApplyAsync(res -> {
throw new RuntimeException("error");
})
.handleAsync((res, err) -> {
if (err != null) {
//handle exception here
return null;
} else {
return res;
}
});
Object result = f2.get();
System.out.println(result);
7.并行执行异步操作并统一处理结果
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
使用如下:
CompletableFuture<String> f1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "world");
CompletableFuture<String> f3 = CompletableFuture.supplyAsync(() -> "!");
// 使用allOf方法
CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
all.get();
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
// 结合StreamAPI
List<String> result = Stream.of(f1, f2, f3)
.map(CompletableFuture::join)
.collect(Collectors.toList());
System.out.println(result);
多线程的框架:
Disruptor( 并发 ):
Disruptor 是一个 Java 的并发编程框架,大大的简化了并发程序开发的难度,在性能上也比 Java 本身提供的一些并发包要好。
https://www.jianshu.com/p/bad7b4b44e48
Netty( 提供异步的、事件驱动的网络应用程序框架 ):
netty4可以说是我这些年做数据采集,TCP、UDP、MQTT等用的最多,api最丰富,性能最好而且轻量级的框架。
https://www.cnblogs.com/hunrry/p/9408394.html
NodeJS(异步):
Node.js发布于2009年5月,由Ryan Dahl开发,是一个基于Chrome V8引擎的JavaScript运行环境,使用了一个事件驱动、非阻塞式I/O模型, [1] 让JavaScript 运行在服务端的开发平台,它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言 。
Vert.x :
近年来,移动网络、社交网络和电商的兴起,使各大服务提供商的客户端请求数量激增,传统服务器架构已不堪重负,致使基于事件和异步的解决方案备受追捧,如Nginx、NodeJS。Vert.x框架基于事件和异步,依托于全异步Java服务器Netty,并扩展了很多其他特性,以其轻量、高性能、支持多语言开发而备受开发者青睐。
多线程的优、缺点:
优点:
- 多线程技术可以加快程序的运行速度,使程序的响应速度更快,因为用户界面可以在进行其它工作的同时一直处于活动状态
- 可以把占据长时间的程序中的任务放到后台去处理,同时执行其他操作,提高效率
- 当前没有进行处理的任务时可以将处理器时间让给其它任务
- 可以让同一个程序的不同部分并发执行,释放一些珍贵的资源如内存占用等等
- 可以随时停止任务
- 可以分别设置各个任务的优先级以优化性能
缺点:
- 因为多线程需要开辟内存,而且线程切换需要时间因此会很消耗系统内存。
- 线程的终止会对程序产生影响
- 由于多个线程之间存在共享数据,因此容易出现线程死锁的情况
- 对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担。
写一句我的感受:
一般情况下我们使用到的多线程,是在该业务场景下,没得办法的选择,所以给我的感觉,多线程是一个进程在多种任务在协同工作时的解决方案,至于解决方案的具体内容那就依靠程序员的经验了,幸好现在最新版的都JDK提供的便捷易懂的API,使得编写难度降低而可读性提高了了。