1 基础准备
1.1 并发与并行
并发是不同的代码块交替执行,也就是交替可以做不同的事情。
并行是不同的代码块同时执行,也就是同时可以做不同的事情。
根据CPU 核数,线程运行是不同的
单核CPU(微观串行,宏观并行)操作系统的任务调度器,将 cpu 的时间片(windows 下时间片最小约为 15 毫秒)分给不同的线程使用,由于时间片切换很快,人的感觉是在并行,实际还是串行执行
的。
多核 cpu下,每个 核(core)
都可以调度运行线程,这时候线程可以是并行的。
1.2 进程、线程
(1)进程
进程基本上是一个正在执行的程序,它是操作系统中最小的资源分配单位。
进程的上下文切换是指 cpu 从一个进程切换到另一个进程。
进程上下文切换主要包含两个主要过程:进程地址空间切换和处理器状态切换。
(2)线程
线程是进程的子集,也称为轻量级进程。一个进程可以有多个线程,这些线程由调度器独立管理。一个进程内的所有线程都是相互关联的。线程是操作系统中最小的调度单位。每个进程至少包含一个线程,每个进程的初始线程被称作主线程。
线程没有自己的地址空间,同一进程的线程之间切换,他们共享同一进程的地址空间,所以只需要切换处理器状态;不同进程的线程之间切换,会引起进程切换。
2 并发实现
2.1 原子操作
用过synchronized 关键字来保证一次只有一个线程在执行代码块。
public synchronized void code() {// TODO
}
Volatile 关键字保证任何线程在读取Volatile修饰的变量的时候,读取的都是这个变量的最新数据。
使用lock锁机制,其中也包括相应的读写锁
2.2 Thread
public class MyRunnable implements Runnable {@Overridepublic void run() {// TODO}
}public class Main {public static void main(String[] args) {Runnable task = new MyRunnable();Thread worker = new Thread(task);worker.setName('Myrunnable');worker.start();
}
new Thread的弊端如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
2.3 Executor
Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public class MyRunnable implements Runnable {@Overridepublic void run() {// TODO}} private static final int NUM = 5;public static void main(String[] args) {ExecutorService pool = Executors.newSingleThreadExecutor();MyRunnable myRunnable = new MyRunnable();pool.submit(myRunnable);if(pool.isShutdown()){// ....}}
2.4 Future
Runnable只是一个接口,里面只是声明了一个run方法,可以看到方法的返回值是void,所以线程执行完了没有任何的返回值;Callable也是一个接口,它里面声明了一个call方法,可以看到它是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
public interface Callable<V> {V call() throws Exception;
}
线程的执行是异步的,一个线程和另外一个线程的执行是互不干扰的,Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,当前线程就开始阻塞,直接call方法结束返回结果。
所以通过实现Callback接口,并用Future可以来接收多线程的执行结果。
主要方法:
- get()方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕
- get(long timeout,TimeUnit unit)获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null,这个就避免了一直获取不到结果使得当前线程一直阻塞的情况发生
- cancel(boolean mayInterruptIfRunning)用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
- 参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。
- 如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false;
- 如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;
- 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true
- isDone()方法判断当前方法是否完成
- isCancel()表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
demo案例:
#假设存在两个任务
public class UserInfoService {public UserInfo getUserInfo(Long userId) throws InterruptedException {Thread.sleep(300);//模拟调用耗时return new UserInfo("666", "捡田螺的小男孩", 27); //一般是查数据库,或者远程调用返回的}
}public class MedalService {public MedalInfo getMedalInfo(long userId) throws InterruptedException {Thread.sleep(500); //模拟调用耗时return new MedalInfo("666", "守护勋章");}
}#实现异步调用
public class FutureTest {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService executorService = Executors.newFixedThreadPool(10);UserInfoService userInfoService = new UserInfoService();MedalService medalService = new MedalService();long userId =666L;long startTime = System.currentTimeMillis();//调用用户服务获取用户基本信息FutureTask<UserInfo> userInfoFutureTask = new FutureTask<>(new Callable<UserInfo>() {@Overridepublic UserInfo call() throws Exception {return userInfoService.getUserInfo(userId);}});executorService.submit(userInfoFutureTask);Thread.sleep(300); //模拟主线程其它操作耗时FutureTask<MedalInfo> medalInfoFutureTask = new FutureTask<>(new Callable<MedalInfo>() {@Overridepublic MedalInfo call() throws Exception {return medalService.getMedalInfo(userId);}});executorService.submit(medalInfoFutureTask);UserInfo userInfo = userInfoFutureTask.get();//获取个人信息结果MedalInfo medalInfo = medalInfoFutureTask.get();//获取勋章信息结果System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");}
}
3 CompletableFuture
3.1 CompletableFuture使用
CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
public class FutureTest {public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {UserInfoService userInfoService = new UserInfoService();MedalService medalService = new MedalService();long userId =666L;long startTime = System.currentTimeMillis();//调用用户服务获取用户基本信息CompletableFuture<UserInfo> completableUserInfoFuture = CompletableFuture.supplyAsync(() -> userInfoService.getUserInfo(userId));Thread.sleep(300); //模拟主线程其它操作耗时CompletableFuture<MedalInfo> completableMedalInfoFuture = CompletableFuture.supplyAsync(() -> medalService.getMedalInfo(userId)); UserInfo userInfo = completableUserInfoFuture.get(2,TimeUnit.SECONDS);//获取个人信息结果MedalInfo medalInfo = completableMedalInfoFuture.get();//获取勋章信息结果System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");}
CompletableFuture提供了几十种方法,辅助我们的异步任务场景。这些方法包括创建异步任务、任务异步回调、多个任务组合处理等方面。
CompletableFuture创建异步任务,一般有supplyAsync和runAsync两个方法
- supplyAsync执行CompletableFuture任务,支持返回值
- runAsync执行CompletableFuture任务,没有返回值。
//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程,根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable)
//自定义线程,根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
3.2 异步回调
1)thenRun/thenRunAsync
通俗点讲就是,做完第一个任务后,再做第二个任务。某个任务执行完成后,执行回调方法;但是前后两个任务没有参数传递,第二个任务也没有返回值
CompletableFuture<Void> cp1 = CompletableFuture.runAsync(() -> {try {//执行任务AThread.sleep(600);} catch (InterruptedException e) {e.printStackTrace();}});CompletableFuture<Void> cp2 = cp1.thenRun(() -> {try {//执行任务BThread.sleep(400);} catch (InterruptedException e) {e.printStackTrace();}});
2)thenAccept/thenAcceptAsync
第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。
CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {return "dev";});CompletableFuture<Void> cp2 = cp1.thenAccept((a) -> {System.out.println("上一个任务的返回结果为: " + a);});cp2.get();
3)thenApply/thenApplyAsync
表示第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。
CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {return "dev";}).thenApply((a) -> {if(Objects.equals(a,"dev")){return "dev";}return "prod";});
3.3 多任务组合回调
thenCombine / thenAcceptBoth / runAfterBoth都表示:「当任务一和任务二都完成再执行任务三」。
区别在于:
- 「runAfterBoth」 不会把执行结果当做方法入参,且没有返回值
- 「thenAcceptBoth」: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
- 「thenCombine」:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
@Testpublic void testCompletableThenCombine() throws ExecutionException, InterruptedException {//创建线程池ExecutorService executorService = Executors.newFixedThreadPool(10);//开启异步任务1CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());int result = 1 + 1;System.out.println("异步任务1结束");return result;}, executorService);//开启异步任务2CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());int result = 1 + 1;System.out.println("异步任务2结束");return result;}, executorService);//任务组合CompletableFuture<Integer> task3 = task.thenCombineAsync(task2, (f1, f2) -> {System.out.println("执行任务3,当前线程是:" + Thread.currentThread().getId());System.out.println("任务1返回值:" + f1);System.out.println("任务2返回值:" + f2);return f1 + f2;}, executorService);Integer res = task3.get();System.out.println("最终结果:" + res);}
applyToEither / acceptEither / runAfterEither 都表示:「两个任务,只要有一个任务完成,就执行任务三」。
区别在于:
- 「runAfterEither」:不会把执行结果当做方法入参,且没有返回值
- 「acceptEither」: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
- 「applyToEither」:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
3.4 其他
Future需要获取返回值,才能获取异常信息
@Testpublic void test() {CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> {if (1 == 1) {throw new RuntimeException("出错了");}return 0.11;});//如果不加 get()方法这一行,看不到异常信息//future.get();}