Java8函数式编程3-并行与并发

参考并建议阅读《Java 8函数式编程》
转载请注明出处:http://blog.csdn.net/cuiods/article/details/53727191


如果将程序比作马拉车,那么并行可以看做试图用很多马拉同一辆车,以提高车速,而单纯的并发则是一匹马同时拉很多车,要做到每辆车兼顾且不翻车。处理很多马拉很多车的问题,可以看作是处理并行与并发的问题。在Java8函数式编程的支持下,如何处理并行与并发问题呢?

一、数据并行化

1、并行化流操作

对于流操作,支持并行化只需要多调用一个方法parallelStream。

public int parallelArraySum() {
    return albums.parallelStream()
                 .flatMap(Album::getTracks)
                 .mapToInt(Track::getLength)
                 .sum();
}

在知道这个方法之后,我们都会倾向于在所有的流方法后加上这个方法,以体现代码的“并行化”特征。然而,在实际运用中如果数据量较少(100?),使用该方法会严重降低效率,只有在处理大量数据时,该方法再能发挥出并行的优势。
使用蒙特卡洛模拟法并行化模拟掷骰子事件:

public Map parallelDiceRolls() {
    double fraction = 1.0 / N;
    return IntStream.range(0, N) 
                    .parallel() 
                    .mapToObj(twoDiceThrows()) 
                    .collect(groupingBy(side -> side, 
                             summingDouble(n -> fraction))); 
}

使用流的并行化方法确实可以大量减少代码行数。

2、限制

调用流的并行方法有如下限制:
(1)Reduce方法的限制:Reduce的初始值必须是组合函数的恒等值。说人话就是初始值与其他值做reduce操作时,结果等于其他值。此外,reduce操作必须符合结合律
(2)避免持有锁。流的并行方法会自行加锁,不要自找麻烦。

3、并行化数组操作

Java8引入了一些针对数组的并行化操作,这些方法添加在Arrays中。
Java8函数式编程3-并行与并发_第1张图片
比如,创建一个数组并初始化数组中的元素:

public static double[] parallelInitialize(int size) {
    double[] values = new double[size];
    Arrays.parallelSetAll(values, i -> i);
    return values;
}

二、编写并发程序

1、非阻塞式IO

通常情况下,我们的应用程序是这样工作的:用户与服务器建立TCP连接,服务器调用方法向用户传输数据,这个方法会阻塞当前线程。这样的方法叫做阻塞式IO。阻塞式IO的缺点是当有大量用户时,用户会和服务器建立大量连接,扩展性不是很好。
非阻塞式IO,又叫异步IO,对于读写的调用立即返回,真正读写的操作在另一个线程完成,这样就可以同时执行其他任务。

2、回调

首先介绍两款用于处理异步操作的框架:
vert.x:https://github.com/eclipse/vert.x
rxjava:https://github.com/ReactiveX/RxJava
使用Vert.x框架接收TCP连接的示例(类似于Servlet)

public class ChatVerticle extends Verticle {
    public void start() {
        vertx.createNetServer()
            .connectHandler(socket -> {
                container.logger().info("socket connected");
                socket.dataHandler(new User(socket, this));
             }).listen(10_000);
        container.logger().info("ChatVerticle started");
    }
}

为connectHandler输入一个lambda表达式,当有用户连接时会调用该表达式,这是一个回调。使用回调的好处是不需要手动管理线程,Vert.x自动帮助我们管理线程。

3、消息传递

在Vert.x中,我们通过事件总线传递消息。
首先需要注册一个回调来写入消息,registerHandler方法将一个程序和一个地址相关联,有消息发给该地址时,就将之作为参数传递给处理程序。

eventBus.registerHandler(user, (Message msg) -> {
    sendClient(msg.body());
});

eventBus是Vert.x的事件总线,它允许在verticle对象之间以非阻塞式IO的方式传递消息。
当想把消息发送给某一个用户时,可以使用代表那个用户的地址发送消息。

eventBus.send(user, name.get() +‘ >’ + message);

使用publish方法可以实现群发功能。

private void broadcastMessage(String message) {
    String name = this.name.get();
    eventBus.publish(name + ".followers", name +‘ >’ + message);
}

verticle对象向事件总线发送消息通信,不需要添加锁或synchronized关键字。
对于并发问题,我现在并没有解决这类问题的实际经验,因此还不能完全理解java对这类问题多出的支持,还是要继续提高姿势水平。


参考并建议阅读《Java 8函数式编程》

你可能感兴趣的:(Java函数式编程)