第六章 Java8与并发
6.1 Java8的函数式编程简介
6.1.1 函数作为一等公民
- 将函数作为参数传递给另外一个函数;函数作为另一个函数的返回值。
6.1.2 无副作用
- 副作用,指的是除了返回值之外,还修改了函数外部的状态。无副作用实际上很难做到。
6.1.3 申明式的(Declarative)
- 函数式编程是申明式的编程方式。相对于命令式而已,命令式程序设计喜欢大量使用可变对象和指令。
- EX:参考253
6.1.4 不变的对象
6.1.5 易于并行
6.1.6 更少的代码
6.2 函数式编程基础
6.2.1 FunctionalInterface 注解
- 函数式接口的概念:只定义了单一抽象方法的接口。接口默认方法和Object实现的方法都不算抽象方法,因此存在多于一个方法的接口也可以为函数式接口。
- 如果一个函数满足函数式接口的定义,即使不使用@FunctionalInterface,编译器依然会把它看作函数式接口。
6.2.2 接口默认方法
6.2.3 lambda表达式
- 即匿名函数。一段没有函数名的函数体
6.2.4 方法引用
- 静态方法引用:ClassName::methodName
- 实例上的实例方法引用:instanceReference::methodName
- 超类上的实例方法引用:super::methodName
- 类型上的实例方法引用:ClassName::methodName
- 构造方法引用:Class::new
- 数组构造方法引用:TypeName[]::new
6.3 一步一步走入函数式编程
- -Djdk.internal.lambda.dumpProxyClass启动lambda表达式中间类型输出。
6.4 并行流与并行排序
6.4.1 使用并行流过滤数据
6.4.2 从集合得到并行流
6.4.3 并行排序
6.5 增强的Future:CompletableFuture
- 实现了Future接口和CompletionStage接口,其为函数式编程的流式调用准备的。
6.5.1 完成了就通知我
- 通过CompletableFuture可以手动设置CompletableFuture的完成状态
6.5.2 异步执行任务
- 注意:java8新增ForkJoinPool.commonPool()方法。它可以获得一个公共的ForkJoin线程池。这个公共线程池中的所有线程都是Daemon线程。这意味着如果主线程退出,这些线程无论是否执行完毕,都会退出系统。
6.5.3 流式调用
6.5.4 CompletableFuture中的异常处理
- exceptionally
6.5.5 组合多个CompletableFuture
6.6 读写锁的改进:StampedLock
- Java8引入的新的一种锁机制。可以认为它是读写锁的一个改进版本。读写锁式悲观策略,读锁会完全阻塞写锁,大量读线程会导致写线程饥饿;StampedLock是一种乐观的策略,这种乐观的锁策略非常类似无锁的操作,使得乐观锁完全不会阻塞写线程。
6.6.1 StampedLock使用示例
EX:参考275
6.6.2 StampedLock 的小陷阱
- StampedLock内部实现时,使用类似于CAS操作的死循环反复尝试的策略。在它挂起线程时,使用的是Unsafe.park函数,在遇到线程中断时,会直接返回。而在StampedLock的死循环逻辑中,没有处理有关中断的逻辑。因此,这就会导致阻塞在park上的线程被中断后,会再次进入循环。而当退出条件得不到满足时,就会发生疯狂占用CPU的情况。
- EX:参考276
6.6.3 有关StampedLock的实现思想
- 内部实现是基于CLH锁的。CLH锁是一种自旋锁,它保证没有饥饿发生,并且可以保证FIFO的服务顺序。
- CLH基本思想如下:
- 锁维护一个等待线程队列,所有申请锁,但是没有成功的线程都记录在这个队列中。每一个节点(代表一个线程),保存一个标记位(locked),用于判断当前线程是否已经释放锁。只要前序节点没有释放锁,则表示当前线程还不能继续执行,因此会自旋等待。
6.7 原子类的增强
6.7.1 更快的原子类:LongAdder
- AtomicInteger存在的问题:高并发竞争激烈时,修改失败的几率会比较高,大量失败修改导致多次循环尝试,因此性能会受到影响。
- 方案:减小锁粒度,分离热点。一种可行的方案就是伪造ConcurrentHashMap,将热点数据分离。如:将AtomicInteger的内部核心数据value分离成一个数组,每个线程访问时,通过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加。热点数据value被分离成多个单元cell,每个cell独自维护内部的值。
- LongAdder的另一种优化手段是避免了伪共享。但并不是padding这种看起来比较碍眼的做法,而是引入了新的注解,@sun.misc.Contended,对Cell进行注解。开启该注解的参数-XX:-ResrictContended,否则,该注解会被忽略。
6.7.2 LongAdder 的功能增强版:LongAccumulator
- 和LongAdder有着共同的父类Striped64.因此其优化同LongAdder一样,将一个long型整数进行分割,存储在不同的变量中,以防止多线程竞争。其只是对LongAdder的扩展,对于LongAdder来说,只是每次对给定的整数执行一次加法,而LongAccumulator则可以实现任意函数操作。
- 构造函数参数:accumulatorFuction为需要执行的二元函数(接收两个long型参数并返回long),第二个参数是初始值。