1.概要
函数式编程——将计算过程抽象成表达式求值。这些表达式由纯数学函数构成,这些数学函数式第一类对象(我们可以像操作数值一样操作第一类对象)并且没有副作用。函数式编程可以更容易做到线程安全,因此特别适合并发编程。之前Java中提到的锁就是为了应对共享的可变状态,但是函数式编程没有可变状态,所以不会遇到由共享可变状态带来的种种问题。
2.可变状态的风险——隐藏和逃逸
我们来回顾一下Java语言中可变状态的问题
2.1 隐藏的可变状态
即使你以为你自己写了一段不存在可变状态的代码,但是仍然可能由于使用 了Java的某些类,使得其在多线程环境是线程非安全的。
2.2 逃逸的可变状态
一段已经同步好的代码,可能由于能返回了一个类对象。而这个类对象由于其内存存在可变状态,导致在其他并发使用的场景造成问题。
除了“隐藏”和“逃逸”这两种可变状态带来的风险以外还有其他风险。使用Clojure可以轻松避免由于可变状态带来的风险。
3. Clojure编写函数式的程序
3.1 clojure语法
clojure的语法都是s-表达式,也就是带括号的列表。(数字字面量除外)
一些基本语法可以查看这篇博文:几分钟内学习 Clojure:
我们关键掌握如何定义常量、函数、控制结构、数组和map对象即可。
3.2 函数式程序
纯粹的函数式语言完全不支持可变量。clojure是不纯粹的函数式语言,他为不同的并发场景提供了大量的多样的可变量。
我们首先用java来实现对数列求和:
public int sum(int[] numbers){
int accmulator=0;
for(int n: numbers)
accumulator+=n;
return accumulator;
}
以上代码在循环中,accumulator是可变的。下面我们看看clojure如何不使用可变状态完成相同的功能。
(defn recursive-sum [numbers]
(if (empty? numbers)
0 ; 判断成立返回0
(+(first numebrs) (recursive-sum rest numbers))))) ;first numbers表示取数组第一个数,rest numbers表示取剩下的数最为函数的输入
另外一个更加简单的写法:
(defn sum [numbers]
(reduce + numebrs)) ; 注意reduce为集合中的每一个元素都调用一次化简函数。这里的化简函数就中间的加符号,注意这里的加符号是个函数
通过和java代码的对比,发现函数式语言并不需要定义变量,通过使用多个函数配合,不断对返回值做计算就可以了,因此,也就不存在可变状态的变量啦。
4 future模型和promise模型
clojure语言是通过这两种模型来完成并发的。其实现在java也支持了,可能由于语言设计本身的限制,用起来没函数语言好用?这个答案我也不知道,也许要深入的学习下和对比才会有结果。
4.1 future模型
Future 表示一个可能还没有实际完成的异步任务的结果,针对这个结果可以添加 Callback 以便在任务执行成功或失败后做出对应的操作。这种模型实际上可以发现,它避免使用共享变量或者线程通信。这样的思想和函数式编程的思想是一致的,即直接获得函数的返回值。很多语言都实现了这种模型,例如clojure。虽然java不是严格意义上的函数式语言,但是也对该模型做了一些基本的支持。下面我用java来看看如何实现该种模型。
import java.util.concurrent.*;
/**
* Created by Kami Wan on 2016/3/4.
*/
class Task implements Callable<Integer> { //Task用来计算1到100求和
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
Thread.sleep(3000);
int sum = 0;
for (int i = 0; i < 100; i++)
sum += i;
return sum;
}
}
public class LearnFuture {
public static void main(String[] args) {
// //方法一:ExecutorService配合Future类来实现
// ExecutorService executor = Executors.newCachedThreadPool();
// Task task = new Task();
// Future<Integer> result = executor.submit(task); //Futue需要配合ExecutorService当中的submit方法使用
// executor.shutdown();
//方法二:ExecutorService类配合FutureTask类来实现
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
executor.shutdown();
//方法三:Thread类配合FutureTask类来实现
// Task task = new Task();
// FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
// Thread thread = new Thread(futureTask); //Thread构造器可以直接接收一个
// thread.start();
// 这一段代码模拟主线程执行一些任务,不加也是可以的
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主线程在执行任务");
try {
//这里我们需要获取之前子线程异步执行的结果
// System.out.println("task运行结果"+result.get()); //方法一
System.out.println("task运行结果" + futureTask.get());//方法二或者方法三
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任务执行完毕");
}
}
4.2 promise模型
promise模型和 future模型的区别
Promise 交由任务执行者,任务执行者通过 Promise 可以标记任务完成或者失败。我们用仍然还是用java来讲解。promise和future是比较接近的概念,stackoverflow上有个提问就是问了这两个概念的不同future和promise之间的不同(注意要翻墙访问)。问答中给对两者的区别主要是这样描述的:future对象是一个存放结果的只读容器,异步操作完毕后,结果就放进去了,只能被读取,因此也不能再对future对象的值做修改了,这个修改的权利只能交给异步操作去完成。promise对象有一个公共的方法允许任何其他函数在任何时间对结果的值做修改。Java8当中的CompletableFuture和SettableFuture可以理解为promises对象,因为他们的值能被设定标记"completed"。也正是如此,CompletableFuture不是一个纯粹的promise对象。这里的纯粹按照我的理解应该就是可以直接修改其值。
简单总结区别就是:
promise对象不一定是future对象的子类。