java8函数式编程简介
函数作为一等公民
一个函数可以作为另一个函数的返回值.
//js中
function f1(){
var n=1;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); //1
无副作用
副作用指的是函数在调用过程中,除了给出返回值,还修改函数外部的状态.
函数式编程认为,函数的副作用应该尽量被避免.
注意:
显示函数:与外界交换数据的唯一渠道是参数和返回值,不会读取或修改函数的外部状态
隐式函数:除参数和返回值外,还会读取/修改外部信息.
声明式
命令式: 使用可变对象和指令
声明式: 需要提供明确的指令操作
//命令式
public static void imperative(){
int[] iArr={1,3,4,5,6,9,7,8,4,2};
for(int i=0;i<iArr.length;i++){
System.out.println(iArr[i]);
}
}
//声明式
public static void declarative(){
int[] iArr={1,3,4,5,6,9,7,8,4,2};
Arrays.stream(iArr).forEach(System.out::println);
}
不变的对象
函数式编程中,几乎所有传递的对象都不会被轻易修改
int[] iArr={1,3,4,5,6,9,7,8,4,2};
Arrays.stream(arr).map((x)->x=x+1).forEach(System.out::println); //输出+1的数组
System.out.println();
Arrays.stream(arr).forEach(System.out::println); //原数组不变
易于并行
不变模式不需要关心线程安全问题,多线程下性能更好.
更少的代码
简洁明了,代码更少
函数式编程基础
FunctionalInterface注释
函数式接口只能有一个抽象方法,任何被Object实现的方法,都不能被视为抽象方法
@FunctionalInterface
public static interface IntHandler{
void handle(int i)
}
@FunctionalInterface
public static interface IntHandler{
void handle(int i);
boolean equals(Object obj); //object中含有的方法,该接口也为函数式接口
}
接口默认方法
java8接口也可以包含多个实例方法,具体实现如下:
public interface IHorse{
void eat();
default void run(){ //接口默认方法
System.out.println("hourserun");
}
}
public interface IAnimal{
default void breath(){ //接口默认方法
System.out.println("breath");
}
}
public class Mule implements IHorse,IAnimal{
@Override
public void eat(){
System.out.println("Mule eat");
}
public static void main(String[] args){
Mule m=new Mule();
m.run();
m.breath();
}
}
lambda表达式
List<Integer> numbers=Arrays.asList(1,2,3,4,5,6);
numbers.forEach((Integer value)-> System.out.println(value));
lambda表达式也可以访问外部的局部变量,但局部变量不允许改变,即final修饰
final int num=2; //不写final也可以,但不允许改变此值
Function<Integer,Integer> stringConverter=(from)->from*num;
System.out.println(stringConverter.apply(3));
方法引用
- 静态方法引用: ClassName::methodName
- 实例方法引用: instanceReference::methodName
- 超类上实例方法引用: super::methodName
- 类型上的实例方法: ClassName::methodName
- 构造方法引用: Class::new
- 数组构造方法引用: TypeName[]::new
public class Test2 {
public static void main(String[] args) {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, "billy" + i));
}
users.stream().map(User::getName).forEach(System.out::println);//调用User的实例方法
}
}
User::getName,表示User类的实例方法,在执行时,自动识别流中的元素作为调用目标还是调用方法的参数.
一般来说,如果使用的是静态方法,或者调用目标明确,那么流内的元素会自动作为参数使用,如果函数引用表示实例方法,并且不存在调用目标,那么流内元素自动作为调用目标
一步一步走入函数式编程
public class Test2 {
static int[] arr = {1, 3, 4, 5, 6, 7, 8, 9, 10};
public static void main(String[] args) {
// ArrayList<User> users = new ArrayList<>();
// for (int i = 0; i < 10; i++) {
// users.add(new User(i, "billy" + i));
// }
// users.stream().map(User::getName).forEach(x -> System.out.println(x));
IntConsumer outprintln = System.out::println; //约等于函数接收函数
IntConsumer errprintln = System.out::println;
Arrays.stream(arr).forEach(outprintln.andThen(errprintln));
}
}
并行流与并行排序
并行流过滤数据
public class PrimeUtil {
public static boolean isPrime(int number) {
int tmp = number;
if (tmp < 2) {
return false;
}
for (int i = 2; Math.sqrt(tmp) >= i; i++) {
if (tmp % i == 0) {
return false;
}
}
return true;
}
}
IntStream.range(1,10000).filter(PrimeUtil::isPrime).count //串行流处理
IntStream.range(1,10000).parallel().filter(PrimeUtil::isPrime).count //并行流处理
并行排序
数组可以用Arrays.sort()进行串行排序,也可以用Arrays.parallelSort()来进行并行排序
Random r=new Random();
Arrays.setAll(arr,i->r.nextInt())
Arrays.parallelSetAll(arr,i->r.nextInt())
增强future:CompletableFuture
完成了就通知
和Future一样,向Completable请求一个数据,如果数据还没有准备好,请求线程就会等待
public class CFutureMain1 {
public static class AskThread implements Runnable {
CompletableFuture<Integer> re = null;
public AskThread(CompletableFuture<Integer> re) {
this.re = re;
}
@Override
public void run() {
int myRe = 0;
try {
myRe = re.get() * re.get(); //CompletableFuture中没有数据时阻塞住
} catch (Exception e) {
}
System.out.println(myRe);
}
}
public static void main(String[] args) throws InterruptedException {
final CompletableFuture<Integer> future = new CompletableFuture<>();
new Thread(new AskThread(future)).start();
// 模拟长时间其他调用
Thread.sleep(1000);
// 告知完成结果
future.complete(60);
}
}
异步执行
public class CFutureMain2 {
public static Integer calc(Integer para) {
try {
// 模拟一个长时间的执行
Thread.sleep(1000);
} catch (InterruptedException e) {
}
return para*para;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
final CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(() -> calc(50)); //异步执行
System.out.println(future.get()); //get()会阻塞,如果不阻塞住线程结束,无法获取值
}
}
异步方法有如下几个:
注意
supplyAsync()方法用于需要返回值的场景,
runAsync()用于没有返回值的场景
可以接受Executor参数则使用指定的线程池,否则使用默认使用ForkJoinPool.commonPool
这个线程池中均为Daemon线程,主线程若退出则所有线程均退出
流式调用
CompletableFuture实现了Future也实现了CompletionStage接口
public class CFutureMain3 {
public static Integer calc(Integer para) {
try {
// 模拟一个长时间的执行
Thread.sleep(1000);
} catch (InterruptedException e) {
}
return para * 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();
}
}
异常处理
public class CFutureMain4 {
public static Integer calc(Integer para) {
return para / 0;
}
public static void main(String[] args) throws InterruptedException,ExecutionException {
CompletableFuture<Void> fu = CompletableFuture
.supplyAsync(() -> calc(50))
.exceptionally(ex -> { //优雅简单
System.out.println(ex.toString());
return 0;
})
.thenApply((i) -> Integer.toString(i))
.thenApply((str) -> "\"" + str + "\"")
.thenAccept(System.out::println);
fu.get();
}
}
组合多个CompletableFuture
- thenCompose
- thenCombine
public class CFutureMain5 {
public static Integer calc(Integer para) {
return para/2;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<Void> fu =
CompletableFuture.supplyAsync(() -> calc(50))
.thenCompose((i)->CompletableFuture.supplyAsync(() -> calc(i)))
.thenApply((str)->"\"" + str + "\"").thenAccept(System.out::println);
fu.get();
}
}
public class CFutureMain6 {
public static Integer calc(Integer para) {
return para / 2;
}
public static void main(String[] args) throws InterruptedException,ExecutionException {
CompletableFuture<Integer> intFuture = CompletableFuture.supplyAsync(() -> calc(50));
CompletableFuture<Integer> intFuture2 = CompletableFuture.supplyAsync(() -> calc(25));
CompletableFuture<Void> fu = intFuture.thenCombine(intFuture2, (i, j) -> (i + j)) //两个执行结果进行相加
.thenApply((str) -> "\"" + str + "\"")
.thenAccept(System.out::println);
fu.get();
}
}
支持timeout的CompletableFuture
在CompletableFuture中添加timeout的功能,超时则直接抛出异常.仅适用于jdk9以上版本
public class CFutureMain7 {
public static Integer calc(Integer para) {
return para / 2;
}
public static void main(String[] args) {
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);
try
{
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
}
读写锁改进:StampedLock
读写锁的一种改进版本,读写锁虽然分离了读写功能,但读写之间依旧冲突,大量的读线程可能会引起写线程的"饥饿".而Stampedock则提供了一种乐观的读策略,类似于无锁,使得乐观锁完全不会阻塞写线程.
使用示例
public 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);
}
}
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的小陷阱
StampedLock内部使用了类似CAS操作的死循环,挂起线程时,使用的是Unsafe.park()函数,park函数被中断时直接返回不抛出异常,而在StampedLock死循环中没有处理中断的逻辑,这会导致再次进入循环条件,疯狂占用cpu
public class StampedLockCPUDemo {
static Thread[] holdCpuThreads = new Thread[3];
static final StampedLock lock = new StampedLock();
public static void main(String[] args) throws InterruptedException {
new Thread() {
public void run() {
long readLong = lock.writeLock();
//LockSupport.parkNanos(600000000000L);
LockSupport.parkNanos(15L*1000*1000*1000); //写锁占用很长时间
lock.unlockWrite(readLong);
}
}.start();
Thread.sleep(100);
for (int i = 0; i < 3; ++i) {
holdCpuThreads[i] = new Thread(new HoldCPUReadThread());//启动读线程
holdCpuThreads[i].start();
}
Thread.sleep(10000);
//线程中断后,会占用CPU
for (int i = 0; i < 3; ++i) {
holdCpuThreads[i].interrupt(); //10秒后打断读线程,cpu使用率会飙升
}
}
private static class HoldCPUReadThread implements Runnable {
public void run() {
long lockr = lock.readLock();
System.out.println(Thread.currentThread().getName()+ " 获得读锁");
lock.unlockRead(lockr);
}
}
}
StampedLock的实现思想
StampedLock的内部实现是基于CLH锁.CLH锁是一种自旋锁,保证FIFO和无饥饿
CLH的基本思想:维护一个等待线程队列,所有申请锁但没成功的线程都记录在队列中.每一个节点保存一个标记位(locked),用于判断当前线程是否已经释放锁.
当一个线程试图获取锁时,获取当前等待队列的尾部节点作为其前序节点,并通过while(pred.locked){}
来判断前序节点是否已经成功释放锁
如果前序节点没有释放锁,当前线程就不能执行,只会自旋等待.
释放锁时,自身节点locked位置标记位false,后续等待线程就能继续执行了.
原子类的增强
更快的原子类:LongAdder
CAS循环的机制若在竞争激烈的情况下失败的概率会更大,从而原子操作就会进行多次循环尝试,性能就会受到影响.
在竞争激烈时一般会采用热点分离的方案来提高性能.
1.减少锁粒度
2.类似于concurrentHashMap,采用分段的形式将热点数据分离.
LongAdder原理: 将核心数据value分离成一个数组,每个线程访问时,通过hash算法映射到其中一个数字进行计数,value被分离成多个单元(cell),每个cell独自维护内部的值,当前对象的实际值由所有的cell累计合成
实际操作中,LongAddr并不是一开始就动用数组进行处理,二是将所有数据都先记录在一个base变量中.一旦发生base变量的修改冲突,就会初始化cell数组,使用新策略,如果发现某一个cell上的更新依旧发生冲突,那么系统就会尝试创建新的cell或者将cell数量加倍,以减少冲突可能.
LongAdder的增强版:LongAccumulator
LongAdder只是每次对给定的整数执行一次加法,LongAccumulator可以实现任意函数操作.
//构造函数第一个参数是需要执行的二元函数(接收两个long型参数并返回long),第二个参数是初始值
public LongAccumulator(LongBinaryOperator accumulatorFunction,long identity)
public class LongAccumulatorDemo {
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);
System.out.println(value+"::"+accumulator.longValue());
});
ts[i].start();
}
for (int i = 0; i < 1000; i++) {
ts[i].join();
}
System.out.println(accumulator.longValue());
}
}
ConcurrentHashMap的增强
foreach操作
reduce操作
reduce操作对map的数据进行处理的同事会将其转为另外一种形式
public class ReduceDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 100; i++) {
map.put(Integer.toString(i), i);
}
int count = map.reduceValues(2, (i, j) -> i + j);
System.out.println(count);
}
}
上述方法是对concurrenthashmap中所有value求和
第一个参数表示并行度,一个并行任务可以处理的元素个数(估算值),如果设置为Long.MAX_VALUE,则表示完全禁用并行,设置为1表示使用最大可能并行.
条件插入
当元素不存在时需要创建并且将对象插入Map中,而当Map中已经存在该元素时,则直接获得当前在Map中的元素,从而避免多次创建.
concurrentHashMap.computeIfAbsent(key,k->new HeavyObject());
search操作
ConcurrentHashMap可以做并发搜索
int found=map.search(2,(str,i)->{
if(i%25==0){
return i;
}
return null;
})
其他方法
mappingCount():返回map中的条目总数,返回long型,该方法不一定精准
newKeySet():返回一个高效并发的HashSet,该方法是一个静态工厂方法,返回一个线程安全的Set.
发布与订阅模式
JDK9中的内容,目前暂不讨论......
待续....
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。