概述

Java8之前用 Future 处理异步请求, 当你需要获取任务结果时, 通常的做法是调用  get(long timeout, TimeUnit unit) 此方法会阻塞当前的线程, 如果任务处理超时, 就会抛出一个  TimeoutException

  1. @Test
  2. public void test1() throws InterruptedException, ExecutionException, TimeoutException {
  3. ExecutorService executorService = Executors.newCachedThreadPool();
  4. Future<String> f = executorService.submit(() -> "ceshishanghu");
  5. String s = f.get(3, TimeUnit.SECONDS);
  6. System.out.println(s);
  7. }

在Java8中引入了 CompletableFuture, 使用它提供的API可以不用像之前那样阻塞式或轮询的获取某个异步任务的结果, CompletableFuture 会在异步任务处理完成后自动进行回调, 让你可以链式的组合多个异步任务。

CompletableFuture 类中提供了许多以 Async 后缀结尾的方法。通常而言,名称中不带 Async 的方法和它的前一个任务一样,在同一个线程中运行。而名称以 Async 结尾的方法会将后续的任务提交到一个线程池,所以每个任务是由不同的线程处理的。

静态工厂方法

  • supplyAsync(): 异步处理任务, 有返回值
  • runAsync(): 异步处理任务, 没有返回值
  • allOf(): 需要等待所有的异步任务都执行完毕,才会返回一个新的CompletableFuture
  • anyOf(): 任意一个异步任务执行完毕,就会返回一个新的CompletableFuture
  • completedFuture(): 这种方式获取的 CompletableFuture 不是异步的,它会等待获取明确的返回结果之后再返回一个已经完成的 CompletableFuture
  1. @Test
  2. public void test2() {
  3. //创建一个已经有任务结果的CompletableFuture
  4. CompletableFuture<String> f1 = CompletableFuture.completedFuture("return value");
  5. //异步处理任务,有返回值
  6. CompletableFuture<String> f2 = CompletableFuture.supplyAsync(this::get);
  7. //异步处理任务,没有返回值
  8. CompletableFuture<Void> f3 = CompletableFuture.runAsync(System.out::println);
  9. //需要等待所有的异步任务都执行完毕,才会返回一个新的CompletableFuture
  10. // CompletableFuture<Void> all = CompletableFuture.allOf(f1, f2, f3);
  11. //任意一个异步任务执行完毕,就会返回一个新的CompletableFuture
  12. CompletableFuture<Object> any = CompletableFuture.anyOf(f1, f2, f3);
  13. Object result = any.join();
  14. System.out.println("result = " + result);//result = return value
  15. }
  16.  
  17. public String get() {
  18. delay();
  19. return "异步任务结果";
  20. }
  21.  
  22. public void delay() {
  23. try {
  24. Thread.sleep(1000L);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }

上面的示例中, allOf() 因为要等待所有的异步任务执行完成,所以要延时1秒钟才会返回一个新的 CompletableFuture, 而 anyOf() 则不需要等待所有的异步任务, 因为第一个异步最先完成, 所以控制台输出  result = return value 。

链式调用

A任务执行完毕, 继续执行B任务, B任务执行完毕, 继续执行C任务...

  1. @Test
  2. public void test2() {
  3. CompletableFuture<Void> f = CompletableFuture.supplyAsync(() -> {
  4. //测试抛异常后,handle()方法接受并处理
  5.        //int x = 1 / 0;
  6. return "这是一个栗子";
  7. }).handle((res, ex) -> {
  8. System.out.println("handle res = " + res);
  9. if (Objects.nonNull(ex)) {
  10. System.out.println("handle ex" + ex.getCause().getMessage());
  11. }
  12. return Objects.nonNull(ex) ? 0 : 1;
  13. }).thenApply(res -> {
  14. System.out.println("thenApply res = " + res);
  15. return res == 1 ? "success" : "error";
  16. }).thenAccept(res -> System.out.println("thenAccept res = " + res)
  17. ).thenRun(() -> System.out.println("没有参数, 异步执行一个没有返回值的任务"));
  18. f.join();
  19. }

输出结果:

  1. handle res = 这是一个栗子
  2. thenApply res = 1
  3. thenAccept res = success
  4. 没有参数, 异步执行一个没有返回值的任务

将上面   int x = 1 / 0; 这行代码取消注释, 重新运行结果如下:

  1. handle res = null
  2. handle ex/ by zero
  3. thenApply res = 0
  4. thenAccept res = error
  5. 没有参数, 异步执行一个没有返回值的任务

可以看到, handle() 方法接受前一个 CompletableFuture  的返回结果或抛出的异常作为方法入参, 经过处理后再返回一个新的结果。

级联组合

  • thenCompose(): 对两个异步操作进行组合,第一个操作完成时,将其结果作为参数传递给第二个操作, 第二个操作会返回一个新的CompletableFuture。
  • thenCombine(): 将两个完全无关联的异步请求的结果整合起来, 计算出一个新的值并返回
  1. @Test
  2. public void test3() {
  3. CompletableFuture<String> f = CompletableFuture.completedFuture("CompletableFuture 1");
  4. CompletableFuture<String> f1 = f.thenCompose(res -> {
  5. System.out.println("thenCompose res = " + res);
  6. return CompletableFuture.supplyAsync(() -> "CompletableFuture 2");
  7. });
  8. System.out.println(f1.join());
  9. CompletableFuture<Integer> f3 = CompletableFuture.completedFuture(998);
  10. CompletableFuture<String> f4 = f.thenCombine(f3, (str, num) -> {
  11. System.out.println("str = " + str + ", num= " + num);
  12. return str + num;
  13. });
  14. System.out.println(f4.join());
  15. }

输出结果:

  1. thenCompose res = CompletableFuture 1
  2. CompletableFuture 2
  3. str = CompletableFuture 1, num= 998
  4. CompletableFuture 1998

whenComplete

当前一个 CompletableFuture  计算完成或抛出异常时, 可以使用 whenComplete() 执行指定的任务。

  1. @Test
  2. public void test4() {
  3. CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> {
  4. //测试抛异常后,whenComplete()方法接受并处理
  5. int x = 1 / 0;
  6. return "这是一个栗子";
  7. }).whenComplete((res, ex) -> {
  8. System.out.println("whenComplete res = " + res);
  9. if (Objects.nonNull(ex)) {
  10. System.out.println("whenComplete ex" + ex.getCause().getMessage());
  11. }
  12. });
  13. System.out.println("f.join() = " + f.join());
  14. }

输出结果如下,其中 res 对应前一个 CompletableFuture 的返回结果,ex 对应前一个 CompletableFuture 抛出的异常(如果发生异常)。

从控制台输出顺序看出,当前一个 CompletableFuture  计算完成或抛出异常时,  whenComplete() 会接受它的返回结果或抛出的异常,来做一些其他的事情,最后再返回原来的返回结果或抛出异常。类比下 try/catch 语句块中的 final 语句块。

  1. whenComplete res = null
  2. whenComplete ex/ by zero
  3.  
  4. java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
  5.  
  6. at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
  7. at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
  8. at java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1592)
  9. at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java)
  10. at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
  11. at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
  12. at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
  13. at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
  14. at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
  15. Caused by: java.lang.ArithmeticException: / by zero
  16. at com.java8.action.ChapterTest.lambda$test4$0(ChapterTest.java:22)

异常处理

只有当前一个 CompletableFuture 发生异常时,才会进入到 exceptionally() 方法,并将产生的异常作为入参。

  1. @Test
  2. public void test5() {
  3. CompletableFuture<String> f = CompletableFuture.supplyAsync(() -> {
  4. //测试抛异常后,exceptionally()方法接受并处理
  5. //int x = 1 / 0;
  6. return "这是一个栗子";
  7. }).exceptionally(ex -> ex.getCause().getMessage());
  8. System.out.println("f.join() = " + f.join());
  9. }

注释  int x = 1 / 0; ,输出如下:

  1. f.join() = 这是一个栗子

取消注释   int x = 1 / 0; , 输出如下:

  1. f.join() = / by zero

Both系列方法

  • thenAcceptBoth(): 等待当前的 CompletableFuture 和另一个 CompletableFuture 执行完成,将它们的返回结果作为入参去执行一个操作,没有返回值
  • runAfterBoth(): 等待当前的 CompletableFuture 和另一个 CompletableFuture 执行完成,然后去执行一个操作,没有返回值

代码清单一

  1. @Test
  2. public void test6() {
  3. CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(9523);
  4. CompletableFuture<String> f2 = CompletableFuture.supplyAsync(this::get);
  5. CompletableFuture<Void> both = f1.thenAcceptBoth(f2, (num, str) -> System.out.println("num = " + num + ", str = " + str));
  6. both.join();
  7. }
  8.  
  9. public String get() {
  10. delay();
  11. return "CompletableFuture 2";
  12. }
  13.  
  14. public void delay() {
  15. try {
  16. Thread.sleep(1000L);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }

代码清单一输出结果如下:

  1. num = 9523, str = CompletableFuture 2 

代码清单二

  1. @Test
  2. public void test7() {
  3. CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(9523);
  4. CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> "");
  5. CompletableFuture<Void> both = f1.runAfterBoth(f2, () -> System.out.println("执行一个任务,没有入参"));
  6. both.join();
  7. }

代码清单二输出结果如下:

  1. 执行一个任务,没有入参

Either系列

  • acceptEither: 当前的 CompletableFuture 和另一个 CompletableFuture 任意一个执行完成,将对应的返回结果作为入参去执行一个操作,没有返回值
  • applyToEither: 当前的 CompletableFuture 和另一个 CompletableFuture 任意一个执行完成,将对应的返回结果作为入参,使用 mapping 函数转换成一个新的值并返回
  • runAfterEither: 当前的 CompletableFuture 和另一个 CompletableFuture 任意一个执行完成,然后去执行一个操作,没有返回值

代码清单三:

  1. @Test
  2. public void test8() {
  3. CompletableFuture<String> f1 = CompletableFuture.completedFuture("CompletableFuture 1");
  4. CompletableFuture<String> f2 = CompletableFuture.supplyAsync(this::get);
  5. CompletableFuture<Void> both = f1.acceptEither(f2, System.out::println);
  6. both.join();
  7. }
  8.  
  9. public String get() {
  10. delay();
  11. return "CompletableFuture 2";
  12. }
  13.  
  14. public void delay() {
  15. try {
  16. Thread.sleep(1000L);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. }

代码清单三输出结果:

  1. CompletableFuture 1

代码清单四:

  1. @Test
  2. public void test9() {
  3. CompletableFuture<String> f1 = CompletableFuture.supplyAsync(this::get);
  4. CompletableFuture<String> f2 = CompletableFuture.completedFuture("CompletableFuture 2");
  5. CompletableFuture<Integer> f3 = f1.applyToEither(f2, res -> {
  6. System.out.println("res = " + res);
  7. return res.length();
  8. });
  9. System.out.println("f3.join() = " + f3.join());
  10. }
  11.  
  12. public String get() {
  13. delay();//这里会延时一秒钟
  14. return "CompletableFuture 1";
  15. }

代码清单四输出结果:

  1. res = CompletableFuture 2
  2. f3.join() = 19

代码清单五:

  1. @Test
  2. public void test10() {
  3. CompletableFuture<String> f1 = CompletableFuture.supplyAsync(this::get);
  4. CompletableFuture<Void> f2 = CompletableFuture.allOf();
  5. CompletableFuture<Void> f3 = f1.runAfterEither(f2, () -> System.out.println("执行一个任务,没有入参"));
  6. f3.join();
  7. }
  8.  
  9. public String get() {
  10. delay();//这里会延时一秒钟
  11. return "CompletableFuture 1";
  12. }

代码清单五输出结果:

  1. 执行一个任务,没有入参

使用自定义的执行器来处理多个异步任务

在实际应用场景中可能会遇到这种情况,假如你需要同时处理大量的异步任务,且这些异步任务互相不依赖,你只要最后把它们的结果组装起来就行,这该怎么实现呢?

下面给出了一个使用默认执行器的示例,通过Stream流同时创建 9 个异步任务,获取它们的结果并组装后返回,其中 Runtime.getRuntime().availableProcessors() 表示Java虚拟机可用的处理器个数,在我之前的文章 Java8系列 (二) Stream流 中有介绍过。

代码清单六:

  1. @Test
  2. public void test11() {
  3. List<String> list = Arrays.asList("王小波书店", "杭州沈记古旧书店", "猫的天空之城概念书店", "纯真年代书吧", "南山书屋", "西西弗书店", "新华书店", "钟书阁", "云门书屋");
  4. System.out.println("当前机器有" + Runtime.getRuntime().availableProcessors() + "个可用的处理器");
  5. long start = System.nanoTime();
  6. List<CompletableFuture<String>> futures = list.stream()
  7. .map(str -> CompletableFuture.supplyAsync(() -> this.calculateLength(str)))
  8. .collect(Collectors.toList());
  9. System.out.println("get futures "+(System.nanoTime() - start) / 1000_000 + " msecs");
  10. String result = futures.stream()
  11. .map(CompletableFuture::join)
  12. .collect(Collectors.joining(",", "[", "]"));
  13. System.out.println("get result "+(System.nanoTime() - start) / 1000_000 + " msecs");
  14. System.out.println(result);
  15. }
  16.  
  17. public String calculateLength(String str) {
  18. delay();
  19. return str;
  20. }
  21.  
  22. public void delay() {
  23. try {
  24. Thread.sleep(1000L);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }

运行代码清单六,输出结果:

  1. 当前机器有4个可用的处理器
  2. get futures 95 msecs
  3. get result 3098 msecs
  4. [王小波书店,杭州沈记古旧书店,猫的天空之城概念书店,纯真年代书吧,南山书屋,西西弗书店,新华书店,钟书阁,云门书屋]

可以看到,虽然使用了异步处理,但还是花了 3098 毫秒才执行完成所有任务。这是因为 CompletableFuture 内部采用的是通用线程池 ForkJoinPool.commonPool() , 默认都使用固定数目的线程, 具体线程数取决于  Runtime.getRuntime().availableProcessors()  的返回值。

我这里测试的机器显示通用线程池中处于可用状态的线程数为 4,一次只能同时处理 4 个任务,后面的5个异步任务只能等到前面某一个操作完成释放出空闲线程才能继续, 因此总的会消耗约 3 秒钟的时间。

我们将上面的代码进行重构,使用自定义的执行器,通过自定义的执行器你可以指定线程池的大小。其中线程数的设定可以参考公式  Nthreads = NCPU * UCPU * (1 + W/C)

  1. @Test
  2. public void test12() {
  3. List<String> list = Arrays.asList("王小波书店", "杭州沈记古旧书店", "猫的天空之城概念书店", "纯真年代书吧", "南山书屋", "西西弗书店", "新华书店", "钟书阁", "云门书屋");
  4. final ExecutorService executor = Executors.newFixedThreadPool(Math.min(list.size(), 100), r -> {
  5. Thread thread = new Thread(r);
  6. //守护线程不会组织程序的终止
  7. thread.setDaemon(true);
  8. return thread;
  9. });
  10. System.out.println("当前机器有" + Runtime.getRuntime().availableProcessors() + "个可用的处理器, 当前处理异步请求的线程池大小为 " + Math.min(list.size(), 100));
  11. long start = System.nanoTime();
  12. List<CompletableFuture<String>> futures = list.stream()
  13. .map(str -> CompletableFuture.supplyAsync(() -> this.calculateLength(str), executor))
  14. .collect(Collectors.toList());
  15. System.out.println("get futures " + (System.nanoTime() - start) / 1000_000 + " msecs");
  16. String result = futures.stream()
  17. .map(CompletableFuture::join)
  18. .collect(Collectors.joining(",", "[", "]"));
  19. System.out.println("get result " + (System.nanoTime() - start) / 1000_000 + " msecs");
  20. System.out.println(result);
  21. }
  22.  
  23. public String calculateLength(String str) {
  24. delay();
  25. return str;
  26. }
  27.  
  28. public void delay() {
  29. try {
  30. Thread.sleep(1000L);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }

输出结果如下:

  1. 当前机器有4个可用的处理器, 当前处理异步请求的线程池大小为 9
  2. get futures 38 msecs
  3. get result 1039 msecs
  4. [王小波书店,杭州沈记古旧书店,猫的天空之城概念书店,纯真年代书吧,南山书屋,西西弗书店,新华书店,钟书阁,云门书屋]

可以看到,使用自定义的执行器调大线程池大小后,总的运行时间只要 1039 毫秒。

将CompletableFuture作为Controller的返回值

上面还存在一个问题,虽然现在可以同时处理多个异步任务,但是如果需要将异步结果返回给另一个服务,那不是还得通过 join() 阻塞的获取到返回值后才能再返回么?

自Spring Boot 1.3 (Spring 4.2) 之后开始支持 CompletableFuture 或 CompletionStage 作为 Controller 的返回值,她很好的解决了上面的异步阻塞问题,只要将  CompletableFuture 作为 Controller 的返回值,在异步任务执行完成后,它会自动响应结果给另一个服务。

  1. @RestController
  2. public class AsyncController {
  3.  
  4. @GetMapping("/redirect")
  5. public CompletableFuture<ModelAndView> redirect() {
  6. return CompletableFuture.supplyAsync(() -> {
  7. this.delay();
  8. RedirectView redirectView = new RedirectView("https://www.cnblogs.com/qingshanli/");
  9. redirectView.addStaticAttribute("hint", "CompletableFuture组装ModelAndView视图,异步返回结果");
  10. return new ModelAndView(redirectView);
  11. });
  12. }
  13.  
  14. @GetMapping("/async")
  15. public CompletableFuture<String> async() {
  16. System.out.println("async method start");
  17. return CompletableFuture.supplyAsync(() -> {
  18. this.delay();
  19. return "CompletableFuture作为Controller的返回值,异步返回结果";
  20. }).whenComplete((res, ex) -> System.out.println("async method completely, res = " + res + ", ex = " + ex));
  21. }
  22.  
  23. public void delay() {
  24. try {
  25. Thread.sleep(3000L);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. } 

启动项目,Postman 访问 http://localhost:8080/async,截图如下:

Postman 访问 http://localhost:8080/redirect,截图如下:

参考资料

https://github.com/AndreasKl/spring-boot-mvc-completablefuture

https://nickebbitt.github.io/blog/2017/03/22/async-web-service-using-completable-future

https://www.humansreadcode.com/spring-boot-completablefuture/

Java8 实战

作者:张小凡
出处:https://www.cnblogs.com/qingshanli/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】。

Java8系列 (七) CompletableFuture异步编程的更多相关文章

  1. 编程老司机带你玩转 CompletableFuture 异步编程

    本文从实例出发,介绍 CompletableFuture 基本用法.不过讲的再多,不如亲自上手练习一下.所以建议各位小伙伴看完,上机练习一把,快速掌握 CompletableFuture. 个人博文地 ...

  2. 带你玩转CompletableFuture异步编程

    前言 最近在忙生活的第一个OKR,这个等等后面具体聊聊,今天开始恢复每周一篇原创,感谢小伙伴的不离不弃.这篇文章也是最近在Code Review的时候,看到的大家代码,想整体推下大家异步编程的思想,由 ...

  3. java并发系列(八)-----java异步编程

    同步计算与异步计算 从多个任务的角度来看,任务是可以串行执行的,也可以是并发执行的.从单个任务的角度来看,任务的执行方式可以是同步的,也可以是异步的. Runnable.Callable.Future ...

  4. 异步编程利器:CompletableFuture

    一.一个示例回顾Future 一些业务场景我们需要使用多线程异步执行任务,加快任务执行速度. JDK5新增了Future接口,用于描述一个异步计算的结果.虽然 Future 以及相关使用方法提供了异步 ...

  5. Java8函数之旅 (八) - 组合式异步编程

    前言 随着多核处理器的出现,如何轻松高效的进行异步编程变得愈发重要,我们看看在java8之前,使用java语言完成异步编程有哪些方案. JAVA8之前的异步编程 继承Thead类,重写run方法 实现 ...

  6. 从CompletableFuture到异步编程设计

    从CompletableFuture到异步编程设计,笔者就分为2部分来分享CompletableFuture异步编程设计,前半部分总结下CompletableFuture使用实践,后半部分分享下Com ...

  7. 有了 CompletableFuture,使得异步编程没有那么难了!

    本文导读: 业务需求场景介绍 技术设计方案思考 Future 设计模式实战 CompletableFuture 模式实战 CompletableFuture 生产建议 CompletableFutur ...

  8. 异步编程系列第04章 编写Async方法

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

  9. 异步编程系列第05章 Await究竟做了什么?

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

随机推荐

  1. Kubernetes 系列(八):搭建EFK日志收集系统

    Kubernetes 中比较流行的日志收集解决方案是 Elasticsearch.Fluentd 和 Kibana(EFK)技术栈,也是官方现在比较推荐的一种方案. Elasticsearch 是一个 ...

  2. MongoDB安装过程中出现service MongoDB failed to start,verify that you have sufficient privileges to start...

    win10系统下,安装MongoDB 64位, service MongoDB failed to start,verify that you have sufficient privileges t ...

  3. Onethink上传服务器后登录不了的问题

    本地修改完Onethink后上传到服务器,进入后台登录的时候,发现输入用户名和密码和验证码后,第一次点击登录没反应,第二次点击提示验证码错误. 经过一研究发现 onethink 的登陆是通过API连接 ...

  4. [LeetCode] 822. Card Flipping Game

    Description On a table are N cards, with a positive integer printed on the front and back of each ca ...

  5. 在Linux环境下采用压缩包方式安装JDK 13

    本文地址:https://www.cnblogs.com/oberon-zjt0806/p/11663731.html 可以,转载,出处,格式,懂?? 什么是JDK?? 好吧如果你不知道这个问题的话我 ...

  6. Spring Boot (十二): Spring Boot 邮件服务

    最早我们发邮件的时候是使用 JavaMail 来发送邮件,而在 Spring Boot 中, Spring Boot 帮我们将 JavaMail 封装好了,是可以直接拿来使用的. 1. 依赖文件 po ...

  7. Jenkins构建Jmeter项目之源代码管理(SVN)

    1.查看项目创建中是否又svn插件,没有的话下载插件subversion 2.配置svn源代码管理,如下图(testcases目录下包含build.xml和脚本文件) 3.查看Jenkins本地工作空 ...

  8. 什么是STM32的ISP?

    上一篇笔记分享了STM32的串口IAP实例:STM32串口IAP分享.其中,下载IAP程序时用ISP的方式进行下载.这里的ISP又是什么呢? ISP方式下载程序原理 ISP:In System Pro ...

  9. procdump64+mimikatz获取win用户hash密码

    1.导出lsass.exe procdump64.exe -accepteula -ma lsass.exe lsass.dmp 2.执行mimikatz mimikatz.exe "sek ...

  10. ArrayList源码解析[一]

    ArrayList源码解析[一] 欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 在工作中集合list集合用的相对来 ...