1 Java8的函数式编程
2 函数式编程基础
2.1 函数式编程特点
- 函数可作为返回值
- 限制副作用:限制修改函数外部的状态的途径
显式函数:函数与外界交换数据的唯一渠道是参数和返回值
隐式函数:除参数和返回值外,还会读取或改变外部信息 - 声明式:操作被封装到程序库中,不需要指定明确的执行语句,只需声明需求
public void imperative() { int[] iArr= {1,3,4,5,6,2}; for(int i=0;i<iArr.length;i++) { System.out.println(iArr[i]); } } public void declarative() { int[] iArr= {1,3,4,5,6,2}; Arrays.stream(iArr).forEach(System.out::println); }
- 不变对象:传递的对象不会轻易修改
- 易于并行:对象处于不变状态,不用考虑一致性问题
- 代码简洁
2.2 FunctionalInterface注释
FunctionalInterface用于注释函数式接口(只有单一抽象方法的接口)
@FunctionalInterface
public static interface IntHandler{
void handle(int i);
}
FunctionalInterface的作用
强制检查一个接口是否符合函数式接口的规范,否则产生编译错误(l类似@override)
函数式接口的判定
- 函数式接口的两个关键:单一和抽象(java8允许接口中存在实例方法)
- 接口中任何被Object实现的方法不能视为抽象方法
//不是函数式接口 interface NonFunc { boolean equals(Object obj); }
2.3 接口默认方法
- Java8之前,接口只能包含抽象方法
- Java8开始,接口可以包含实例方法
接口默认方法实现
使用default关键字在接口中定义实例方法
public interface IHorse{
void eat();
default void run(){
System.out.printIn("hourse run");
}
}
默认接口方法实现类多继承功能
类似多继承,Mule同时具有IHorse和IAnimal的实例方法
默认接口方法解决多继承的问题
当类实现的接口中有多个相同的default方法,实现类必须重写该方法.
public class Mule implements IHorsefIDonkey,lAnimal{
@Override
public void run(){
IHorse.super.run();//沿用IHorse的run()
}
@Override
public void eat() {
System.out.println('Mule eat');
}
}
Comparator接口新增方法
- 再次比较方法
default Comparator<T> thenComparing(Comparator<? super T> other) { Objects.requireNonNull(other); return (Comparator<T> & Serializable) (c1, c2) -> { int res = compare(c1, c2); return (res != 0) ? res : other.compare(c1, c2); }; }
- 字符串长度排序,继而字符顺序排序的比较器
Comparator<String> cmp = Comparator.comparinglnt(String;:length).thenComparing(String.CASE_INSENSITIVE_ORDER);
3 Lambda表达式与方法引用
https://mp.youkuaiyun.com/mdeditor/103108043#
4 stream(流)
Stream就像一个高级版本的迭代器,用户对其包含的元素制定需要执行的操作,Stream会在内部隐式进行遍历,做出相应的数据转换,再执行操作。
参考文章
非常感谢https://www.jianshu.com/p/eb331282f2f3等一系列文章
非常感谢https://blog.youkuaiyun.com/qq_28410283/article/details/81052881等一系列文章
4.1流的基本知识
流的分类
- stream顺序流
流中元素顺序与数组元素相同 - parallelStream并行流
将原来的流分为多束流返回,用于并行操作 - unorderedStream无序流
将原来的流中元素打乱后返回新的流
流的操作
-
终端操作
Iterator<T> iterator();//返回迭代器 Spliterator<T> spliterator();//返回spliterator
消耗流,并产生一个结果
-
中间操作
S sequential();//返回顺序流 S parallel();//返回并行流 S unordered();//返回无序流
消耗流,并产生新的流
中间操作
- 无状态操作
处理流中的元素时,会对当前的元素进行单独处理 - 有状态操作
元素的处理可能依赖于其他元素,如最值(并行操作需要多次处理)
4.2 流操作
4.2.1 流的常用操作
流元素操作
Stream<T> filter(Predicate<? super T> predicate);//筛选复合条件的流元素
boolean test(T t);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);//对流元素进行映射
R apply(T t);
Stream<T> distinct();//流元素唯一
Stream<T> sorted(Comparator<? super T> comparator);//流元素排序
int compare(T o1, T o2);
Stream<T> limit(long maxSize);//截断流
void forEach(Consumer<? super T> action);//消费流元素
void accept(T t);
类型转换
1. list->ref_arr
Integer[] irs=l.stream().toArray(Integer[]::new);
Integer[] irs= l.toArray(new Integer[0]);
2. list->arr
int[] is=l.stream().mapToInt(Integer::valueOf).toArray(int[]::new);
3. arr->ref_arr
int[] is = Arrays.stream(irs).mapToInt(Integer::valueOf).toArray();
4. arr->list
List<Integer> list1 = Arrays.stream(arr).boxed().collect(Collectors.toList());
5. ref_arr->list
List<Integer> l= Arrays.asList(irs);
6. ref_arr->arr
Integer[] irs = Arrays.stream(is).boxed().toArray(Integer[]::new);
4.2.2 流的缩减操作
https://www.jianshu.com/p/7c76d1940ea9
将流缩减为一个值,是终端操作(min()/count()等)
通用的缩减操作:reduce
public interface Stream<T> extends BaseStream<T, Stream<T>> {
...
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
}
代码分析
- reduce(base,(a,b)->fun(a,b))
最初,base作为a,流中第一个元素作为b
fun的返回值作为a,流中下一个元素作为b,直到结束int[] arr={1,2,3,4,5,6}; System.out.println(Arrays.stream(arr).reduce(0,(a,b)->a+b);//求和结果21 System.out.println(Arrays.stream(arr).reduce(1,(a,b)->a*b);//求积结果720
- reduce((a,b)->fun(a,b))
最初,流中第一个元素作为a,第二个元素作为b
fun的返回值作为a,流中下一个元素作为b,直到结束
缩减操作的约束
3. 无状态:元素的处理不依赖其他元素
4. 不干预:操作不会影响原始数据
5. 关联性(结合性):最终结果与数据操作的顺序无关(便于并行)
4.2.3 流的收集操作
原文链接https://blog.youkuaiyun.com/qq_28410283/article/details/81052881
<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);//常用
stream中的collect操作可以使用Collectors静态工厂类中的方法
4.3 并行流
并行流的获取
- Collection接口提供的parallelStream()
- Stream接口提供的parallel()
并行流中的reduce
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner);
List<Integer> lists = new ArrayList<>();
lists.add(1);
lists.add(2);
lists.add(3);
Integer product = lists.parallelStream().reduce(1, (a, b) -> a * (b * 2),
(a, b) -> a * b * 2 );
累加器部分(水平向右)
accumulator
-----------------------------›
thread-1: 1 * 1 * 2 = 2 | 合并器方向(竖直向下)
thread-2: 1 * 2 * 2 = 4 * 2 | combiner
thread-3: 1 * 3 * 2 = 6 * 2 | 因此最终的答案是2 * ( 4 * 2 ) * (6 * 2) = 192
代码解析
- 并行reduce只有Stream类才有
- identity只作用在accumulator上,不作用在combiner上
并行排序
Arrays.parallelSort(T[],Comparator<T>);
原始类型数组在并行排序时,不能指定比较器
并行赋值
public static void setAll(int[]! array, IntUnaryOperator generator)
Random r=new Random();
Arrays, parallelSetAll (arr, (i)->r.nextlnt()));//给元素赋随机值
5 增强的Future:CompletableFuture
参考文章
感谢https://www.cnblogs.com/happyliu/archive/2018/08/12/9462703.html
5.1 CompletableFuture介绍
Future接口的局限性
只能处理单任务,不能组合多个任务
- 将多个异步计算合并为一个
- 等待 Future 集合中的所有任务都完成。
- 仅等待 Future 集合中最快结束的任务完成,并返回它的结果。
CompletableFuture类图
- CompletionStage中所有方法都有普通模式和异步模式(方法后+Asyn)
- CompletionStage可以在方法中指定执行的Executor,默认为系统公共的ForkJoinPool.common线程池
ForkJoinPool,commonPool()获取公共ForkJoin线程池,所有线程为Daemon线程
5.2 CompletableFuture使用
流式调用
public static Integer calc(Integer para) {
try {
// 模拟一个长时间的执行
Thread.sleep(para*10);
} catch (InterruptedException e) {
}
return para*para+1/para;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<Void> fu=CompletableFuture.supplyAsync(() -> calc(50))
.thenApply((i)->Integer.toString(i))
.thenApply((str)->"\""+str+"\"")
.thenAccept(System.out::println);
fu.get();
}
输出:"2500"
组合调用
-
or
public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu=CompletableFuture.supplyAsync(() -> calc(50)) .applyToEither(CompletableFuture.supplyAsync(() -> calc(10)),(x)->x) .thenAccept(System.out::println); fu.get(); } 输出:100
-
and
public static void main(String[] args) throws InterruptedException, ExecutionException { CompletableFuture<Void> fu=CompletableFuture.supplyAsync(() -> calc(50)) .thenAcceptBoth(CompletableFuture.supplyAsync(() -> calc(10)),(x,y)->System.out.println(x+","+y)); fu.get(); } 输出:2500,100
异常处理
public static void main(String[] args) throws InterruptedException,ExecutionException {
CompletableFuture<Void> fu = CompletableFuture
.supplyAsync(() -> calc())
.exceptionally(ex -> {
System.out.println(ex.toString());
return 0;
})
fu.get();
}
输出: java.lang.ArithmeticException: / by zero
timeout(JDK9)
public static void main(String[] args) {
CompletableFuture<Integer> fu=CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
return calc(50);
}).orTimeout(1, TimeUnit.SECONDS).exceptionally(e -> {
System.err.println(e);
return 0;
}).thenAccept(System.out::println);
fu.get();
}
输出:超时异常
6 读写锁改进:StampedLock
参考文章https://segmentfault.com/a/1190000015808032?utm_source=tag-newest
6.1 StampedLock介绍
ReentrantReadWriteLock的不足
- 读写锁虽然分离了读和写的功能,读读之间可以并发,但是读写之间冲突
- 读锁会完全阻塞写锁,且使用的是悲观策略
- 在读线程非常多,写线程很少的情况下,很容易导致写线程“饥饿”
StampedLock官方示例
class Point {
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) {
long stamp = sl.writeLock(); //涉及对共享资源的修改,使用写锁-独占操作
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
/**
* 使用乐观读锁访问共享资源
* 注意:乐观读锁在保证数据一致性上需要拷贝一份要操作的变量到方法栈,并且在操作数据时候可能其他写线程已经修改了数据,
* 而我们操作的是方法栈里面的数据,也就是一个快照,所以最多返回的不是最新的数据,但是一致性还是得到保障的。
*
* @return
*/
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // 使用乐观读锁
double currentX = x, currentY = y; // 拷贝共享资源到本地方法栈中
if (!sl.validate(stamp)) { // 如果有写锁被占用,可能造成数据不一致,所以要切换到普通读锁模式
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp); //读锁转换为写锁
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
} else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
}
StampedLock访问模式
- Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
- Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
- Optimisticreading(乐观读模式):这是一种优化的读模式。
Optimistic Reading模式
“Optimistic reading”乐观读锁,使得读写可以并发执行,但是“Optimistic reading”的使用必须遵循以下模式:
double distanceFromOrigin() {
long stamp = lock.tryOptimisticRead(); // 非阻塞获取版本信息
copyVaraibale2ThreadMemory(); // 拷贝变量到线程本地堆栈
if(!lock.validate(stamp)){ // 校验
long stamp = lock.readLock(); // 获取读锁
try {
copyVaraibale2ThreadMemory(); // 拷贝变量到线程本地堆栈
} finally {
lock.unlock(stamp); // 释放悲观锁
}
}
useThreadMemoryVarables(); // 使用线程本地堆栈里面的数据进行操作
}
StampedLock特点
- 所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为0表示获取失败,其余都表示成功;
- 所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
- StampedLock是不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
- StampedLock支持读锁和写锁的相互转换
RRW中,当线程获取到写锁后,可以降级为读锁,但是读锁是不能直接升级为写锁的。 - StampedLock提供了读锁和写锁相互转换的功能,使得该类支持更多的应用场景。
- StampedLock锁不支持Conditon等待
6.2 StampedLock原理
StampedLock 的内部实现是基于 CLH 锁的。 CLH 锁是一种自旋锁,保证没有饥饿和 FIFO 的服务顺序。
CLH基本思想
-
锁维护一个等待线程队列, 所有申请锁但是没有成功的线程都记录在这个队列中。
-
每一个节点( 一个节点代表一个线程) 保存一个标记位 (locked) ,用于判断当前线程是否己经释放锁
获取锁时,检查前节点locked==false?
释放锁时,修改自身locked=false
-
一个线程试图获得锁时, 取得当前等待队列的尾部节点作为其前序节点, 并判断前序节点是否己经成功释放锁.若节点长时间不能获取锁,则挂起该线程.
while (pred.locked) {}
StampedLock结构
- 等待队列结构
/** Wait nodes */ static final class WNode { volatile WNode prev; volatile WNode next; volatile WNode cowait; // list of linked readers volatile Thread thread; // non-null while possibly parked volatile int status; // 0, WAITING, or CANCELLED final int mode; // RMODE or WMODE WNode(int m, WNode p) { mode = m; prev = p; } } /** Head of CLH queue */ private transient volatile WNode whead; /** Tail (last) of CLH queue */ private transient volatile WNode wtail;
- state字段
state倒数第8位 为1,表示写锁占用;
为0,后7位表示当前读取线程数量;private transient volatile long state;
- 乐观读
public long tryOptimisticRead() ( long s; return (((s = state) & WBIT) OL) ? (s & SBITS) : OL; }
- 乐观读后,有线程申请写锁
writeLock()会将写锁位设置为1,乐观锁validate时,会检查这个改动public long writeLock() { long s, next; // bypass acquireWrite in fully unlocked case only return ((((s = state) & ABITS) == 0L && U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?//写锁位设置为1 next : acquireWrite(false, 0L)); }
乐观锁失败后,提升为悲观读锁public boolean validate(long stamp) { U.loadFence(); return (stamp & SBITS) == (state & SBITS); }
若readLock成功,会将state+1,否则调用acquireRead()public long readLock() { long s = state, next; // bypass acquireRead on common uncontended case return ((whead == wtail && (s & ABITS) < RFULL && U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ? next : acquireRead(false, 0L)); }
acquireRead()方法的工作流程(acquireWrite()类似)
StampedLock陷阱
- StampedLock 内部实现时, 使用类似于 CAS 操作的死循环反复尝试的策略。
- 在挂起线程时, 使用的是 Unsafe.park()函数, 而 park()函数在遇到线程中断时, 会直接返回(不会抛出异常)
- 线程被中断后,再次进入循环,导致大量CPU占用(尤其是多个阻塞线程同时被中断时)
7 原子类增强
7.1 更快的原子类:LongAdder(Java8)
Atomiclnteger 的缺陷
- 在竞争不激烈时,修改成功概率高,远超于锁的性能
- 竞争激烈时,大量修改失败导致多次循环尝试,性能受到影响
LongAdder核心思想
- 当多个线程对value进行更新时,会对cells数组中对应的Cell的value进行更新.
- 如果两个线程对同一个Cell操作,则会引以cells数组扩容
- 在获取value时,需要对base和各个Cell进行sum求和
LongAdder主要方法
- add
public void add(long x) { Cell[] as; long b, v; int[] hc; Cell a; int n; if ((as = cells) != null || !casBase(b = base, b + x)) { boolean uncontended = true; if ( as == null || (n = as.length) < 1 || (a = as[getProbe() &m]) == null || !(uncontended = a.cas(v = a.value, v + x))) longAccumulate(x, hc, uncontended); } }
- 若cells为空,说明没有冲突(竞争不激烈),直接对base进行CAS修改
- 竞争激烈时,对cells数组中的Cell修改
- longAccumulate
- sum
sum求值没有对base和cells进行同步,可能存在一致性问题public long sum() { long sum = base; Cell[] as = cells; if (as != null) { int n = as.length; for (int i = 0; i < n; ++i) { Cell a = as[i]; if (a != null) sum += a.value; } } return sum; }
Cell结构
@sun.misc.Contended
static final class Cell {
volatile long p0, p1, p2, p3, p4, p5, p6;
volatile long value;
volatile long q0, q1, q2, q3, q4, q5, q6;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
声明了多个变量,解决伪共享问题
7.2 LongAdder功能增强:LongAccumulator
LongAccumulator是LongAdder的增强版.
- LongAdder只支持加减运算
- LongAccumulator支持任意函数操作
构造函数
public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity)
- accumulatorFunction是二元函数
- identity是初始值
示例
public static void main(String[] args) throws Exception {
LongAccumulator accumulator = new LongAccumulator(Long::max, Long.MIN_VALUE);
Thread[] ts = new Thread[1000];
for (int i = 0; i < 1000; i++) {
ts[i] = new Thread(() -> {
Random random = new Random();
long value = random.nextLong();
accumulator.accumulate(value);
});
ts[i].start();
}
for (int i = 0; i < 1000; i++) {
ts[i].join();
}
System.out.println(accumulator.longValue());
}
输出随机数中最大值