《Java并发编程实战》21到25讲学习总结

第21讲心得

该讲介绍了原子类。

  1. Java SDK 并发包将无锁方案封装提炼之后,实现了一系列的原子类。其实原子类性能高的秘密很简单,硬件支持而已。CPU 为了解决并发问题,提供了 CAS 指令(CAS,全称是 Compare And Swap,即“比较并交换”)。CAS 指令包含 3 个参数:共享变量的内存地址 A、用于比较的值 B 和共享变量的新值 C;并且只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。作为一条 CPU 指令,CAS 指令本身是能够保证原子性的。
  2. Java SDK 并发包里提供的原子类内容很丰富,我们可以将它们分为五个类别:原子化的基本数据类型、原子化的对象引用类型、原子化数组、原子化对象属性更新器和原子化的累加器。。
  3. 原子化的基本数据类型有 AtomicBoolean、AtomicInteger 和 AtomicLong。
  4. 原子化的对象引用类型有 AtomicReference、AtomicStampedReference 和 AtomicMarkableReference。AtomicStampedReference 和 AtomicMarkableReference 这两个原子类可以解决 ABA 问题。AtomicStampedReference 实现的 CAS 方法就增加了版本号参数,AtomicMarkableReference 的实现机制则更简单,将版本号简化成了一个 Boolean 值。
    
    boolean compareAndSet(
      V expectedReference,
      V newReference,
      int expectedStamp,
      int newStamp) 
    
    
    boolean compareAndSet(
      V expectedReference,
      V newReference,
      boolean expectedMark,
      boolean newMark)

     

第22讲心得

该讲介绍了线程池。

  1. Java 提供的线程池相关的工具类中,最核心的是 ThreadPoolExecutor,通过名字你也能看出来,它强调的是 Executor,而不是一般意义上的池化资源。ThreadPoolExecutor 的构造函数非常复杂,最完备的构造函数有 7 个参数。
  2. corePoolSize表示线程池保有的最小线程数。maximumPoolSize表示线程池创建的最大线程数。keepAliveTime 和 unit 定义了空闲时间。workQueue表示工作队列。threadFactory可以自定义如何创建线程。handler可以自定义任务的拒绝策略。
  3. 考虑到 ThreadPoolExecutor 的构造函数实在是有些复杂,所以 Java 并发包里提供了一个线程池的静态工厂类 Executors,利用 Executors 你可以快速创建线程池。不过目前大厂的编码规范中基本上都不建议使用 Executors 了。
  4. 不建议使用 Executors 的最重要的原因是:Executors 提供的很多方法默认使用的都是无界的 LinkedBlockingQueue,高负载情境下,无界队列很容易导致 OOM,而 OOM 会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列。
  5. 使用有界队列,当任务过多时,线程池会触发执行拒绝策略,线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 它,所以开发人员很容易忽略。因此默认拒绝策略要慎重使用。如果线程池处理的任务非常重要,建议自定义自己的拒绝策略;并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用。
  6. 使用线程池,还要注意异常处理的问题,例如通过 ThreadPoolExecutor 对象的 execute() 方法提交任务时,如果任务在执行的过程中出现运行时异常,会导致执行任务的线程终止;不过,最致命的是任务虽然异常了,但是你却获取不到任何通知,这会让你误以为任务都执行得很正常。虽然线程池提供了很多用于异常处理的方法,但是最稳妥和简单的方案还是捕获所有异常并按需处理,你可以参考下面的示例代码。
    
    try {
      //业务逻辑
    } catch (RuntimeException x) {
      //按需处理
    } catch (Throwable x) {
      //按需处理
    } 

     

第23讲心得

该讲介绍了Future,它可以很容易获得异步任务的执行结果。

  1. Future 接口有 5 个方法,它们分别是取消任务的方法 cancel()、判断任务是否已取消的方法 isCancelled()、判断任务是否已结束的方法 isDone()以及2 个获得任务执行结果的 get() 和 get(timeout, unit),。
  2. ThreadPoolExecutor 提供的 3 个 submit() 方法返回值皆为Future 。提交 Runnable 的方法的参数是一个 Runnable 接口,Runnable 接口的 run() 方法是没有返回值的,所以 submit(Runnable task) 这个方法返回的 Future 仅可以用来断言任务已经结束了,类似于 Thread.join()。提交 Callable 的方法的参数是一个 Callable 接口,它只有一个 call() 方法,并且这个方法是有返回值的,所以这个方法返回的 Future 对象可以通过调用其 get() 方法来获取任务的执行结果。提交 Runnable 任务及结果引用 的方法很有意思。假设这个方法返回的 Future 对象是 f,f.get() 的返回值就是传给 submit() 方法的参数 result。这个方法该怎么用呢?下面这段示例代码展示了它的经典用法。需要你注意的是 Runnable 接口的实现类 Task 声明了一个有参构造函数 Task(Result r) ,创建 Task 对象的时候传入了 result 对象,这样就能在类 Task 的 run() 方法中对 result 进行各种操作了。result 相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据。
    
    ExecutorService executor 
      = Executors.newFixedThreadPool(1);
    // 创建Result对象r
    Result r = new Result();
    r.setAAA(a);
    // 提交任务
    Future<Result> future = 
      executor.submit(new Task(r), r);  
    Result fr = future.get();
    // 下面等式成立
    fr === r;
    fr.getAAA() === a;
    fr.getXXX() === x
    
    class Task implements Runnable{
      Result r;
      //通过构造函数传入result
      Task(Result r){
        this.r = r;
      }
      void run() {
        //可以操作result
        a = r.getAAA();
        r.setXXX(x);
      }
    }

     

  3. FutureTask 是一个实实在在的工具类,这个工具类有两个构造函数,它们的参数和前面介绍的 submit() 方法类似。那如何使用 FutureTask 呢?其实很简单,FutureTask 实现了 Runnable 和 Future 接口,由于实现了 Runnable 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;又因为实现了 Future 接口,所以也能用来获得任务的执行结果。下面的示例代码是将 FutureTask 对象提交给 ThreadPoolExecutor 去执行。
    
    // 创建FutureTask
    FutureTask<Integer> futureTask
      = new FutureTask<>(()-> 1+2);
    // 创建线程池
    ExecutorService es = 
      Executors.newCachedThreadPool();
    // 提交FutureTask 
    es.submit(futureTask);
    // 获取计算结果
    Integer result = futureTask.get();

     

第24讲心得

本讲介绍了CompletableFuture,它实现了异步编程。

  1. 创建 CompletableFuture 对象主要靠下面代码中展示的这 4 个静态方法。
    
    //使用默认线程池
    static CompletableFuture<Void> 
      runAsync(Runnable runnable)
    static <U> CompletableFuture<U> 
      supplyAsync(Supplier<U> supplier)
    //可以指定线程池  
    static CompletableFuture<Void> 
      runAsync(Runnable runnable, Executor executor)
    static <U> CompletableFuture<U> 
      supplyAsync(Supplier<U> supplier, Executor executor)  

    runAsync(Runnable runnable)和supplyAsync(Supplier<U> supplier)的区别是:Runnable 接口的 run() 方法没有返回值,而 Supplier 接口的 get() 方法是有返回值的。前两个方法和后两个方法的区别在于:后两个方法可以指定线程池参数。

  2. CompletionStage 接口可以清晰地描述任务之间的时序关系。
  3. 串行关系主要是 thenApply、thenAccept、thenRun 和 thenCompose 这四个系列的接口。 AND 汇聚关系主要是 thenCombine、thenAcceptBoth 和 runAfterBoth 系列的接口。OR 汇聚关系主要是 applyToEither、acceptEither 和 runAfterEither 系列的接口。
  4. Function、Consumer、Runnable都不允许抛出可检查异常,但是却无法限制它们抛出运行时异常。非异步编程里面,我们可以使用 try{}catch{}来捕获并处理异常。CompletionStage 接口给我们提供的方案非常简单,比 try{}catch{}还要简单,下面是相关的方法,使用这些方法进行异常处理和串行操作是一样的,都支持链式编程方式。
    
    CompletionStage exceptionally(fn);
    CompletionStage<R> whenComplete(consumer);
    CompletionStage<R> whenCompleteAsync(consumer);
    CompletionStage<R> handle(fn);
    CompletionStage<R> handleAsync(fn);

     

第25讲心得

本讲介绍了CompletionService,它用于需要批量提交异步任务的时候。

  1. CompletionService 接口的实现类是 ExecutorCompletionService,这个实现类的构造方法有两个,分别是:ExecutorCompletionService(Executor executor);ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)。
  2. CompletionService 接口提供的方法有 5 个,这 5 个方法的方法签名如下所示。
    
    Future<V> submit(Callable<V> task);
    Future<V> submit(Runnable task, V result);
    Future<V> take() 
      throws InterruptedException;
    Future<V> poll();
    Future<V> poll(long timeout, TimeUnit unit) 
      throws InterruptedException;

     

  3. submit() 相关的方法有两个。一个方法参数是Callable<V> task,另外一个方法有两个参数,分别是Runnable task和V result,这个方法类似于 ThreadPoolExecutor 的 <T> Future<T> submit(Runnable task, T result) 。
  4. 其余的 3 个方法都是和阻塞队列相关的,take()、poll() 都是从阻塞队列中获取并移除一个元素;它们的区别在于如果阻塞队列是空的,那么调用 take() 方法的线程会被阻塞,而 poll() 方法会返回 null 值。 poll(long timeout, TimeUnit unit) 方法支持以超时的方式获取并移除阻塞队列头部的一个元素,如果等待了 timeout unit 时间,阻塞队列还是空的,那么该方法会返回 null 值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值