《java in action》2的一些记录

java迭代的趋势

更好的并发

流的引入,lambda,一切都是在为了适应现在的硬件架构: 多核,分布式网络架构。即期望给用户提供“轻松”,“安全”的并发编程接口。 流处理,几乎免费的并行,用户在高层次写逻辑代码,具体的执行由底层的lib来选择最合适的执行方式,比如把计算分布到不同的cpu核上。吸取函数式语言里高阶函数,增加了lambda表达式和函数Function,增强了java语言的抽象能力,表达能力,“把行为作为参数传递给函数”,即高阶函数的能力,又称为“行为参数化“(与此对应的还有”类型参数化“,即泛型),自此函数也成为了java语言的一等公民,但是还有方法和类仍然是二等公民,不过提供了一些设施,将二等公民转化为一等公民。
基于Stream的并发,很少使用synchronized关键字,因为是不同的指导思想,stream的并发关注数据分块 而不是 协调访问 , 这样就像函数式编程在靠齐,“无共享可变数据” + “高阶函数” ,不使用synchronized,来协调共享数据的访问(互斥与并发),而是将数据拆分,不共享。

image.png

流的与集合的一个差异是: 流用于表达计算,集合的元素是计算完之后添加进来或者删除的,但是流是在固定的数据结构上,不能直接删除和增加,按需计算,定义流的时候计算并不发生,且只能遍历一次生成新的流(Java里是这样,scala里面的流可以多次使用)。
使用流的好处,是能写出具有如下特点的代码:

  • 声明式:
  • 可复合:
    • 抽象度高
  • 免费并行:

流的定义是: 从支持数据处理的源生成特定的元素序列。定义决定设计


image.png

如果自己设计一条流,也应该按照这样的思路来。

所以,流的使用一般就是三件事:

  • 定义数据源
  • 定义中间操作链 形成一条流水线
  • 终端操作 执行流水线(按需计算)生成结果

无状态流有状态流的区别
有:流内部的算子有用户提供的lambda, 或者 方法引用(方法不是纯函数)
无:没有内部状态,没有用户提供的lambda或者方法引用,没有内部可变状态。
无状态流对并行友好,无缝切换到parallel,而有状态的流不行,比如求和,如果用外部变量进行累加,则parallel很容易出错,但是如果是利用reduce的分开累加,最终将每个累加结果再累加,就不会有并发问题。

数值流存在的原因不是流的复杂性,而是 基本类型和对应的对象类型 之间的装箱和拆箱性能。

流的生成: 万物皆可流

万物都可以作为流的元素,也可以作为流的源头生成流元素。

  • 数值
  • 集合
  • 文件
  • 空对象
  • 函数 (比如无限流,Stream.iterate(0, n -> n + 2)偶数的无限流, Stream.iterate(new int[]{0,1}, t-> new int[]{t[1], t[0] + t[1]}) 斐波那契流, Stream.generate(Math::random) 随机流

流收集:最终计算: 归约

流水线是lazy的数据集的计算迭代器,最终的计算由 terminal action出发,通用的操作即collect,collect接受一个参数Collector来表示最终的流元素去往何处。
Collectors工具类提供了许多直接的预定义的归约器,也提供了一些高阶方法生成归约器,而这一切都离不开背后的基本归约方法:java.util.stream.Collectors#reducing(U, java.util.function.Function<? super T,? extends U>, java.util.function.BinaryOperator<U>)
U 归约的初始元素
Function是将流内元素转化为待归约的元素
BinaryOperator是待归约元素的计算

归约计算的一个目的,收集,也可由归约完成。这就涉及到范畴论的理论来,以数组的收集举例:

reducing(new List<>(),
                    (l, e) -> { l.add(e); return l;}, 
                  (l1, l2) -> { List l = new List(); 
                                    l.addAll(l1);
                                    l.addAll(l2); 
                                    return l;} }

并且上面的归约属于“无状态”,可以轻松的用来做 并行。
reducing这个方法之所以能够作为基本方法是它提供了两个基本能力:

  1. 元素到范畴的映射
  2. 范畴到范畴的映射

代码实际实现是Collector类,另外Stream提供了一个collect方法,接受三个参数- supplier, accumulator 和 combiner,来自定义收集,其语义和Collector接口相应方法返回的函数完全相同。

分组: Collector的连接 : groupingBy( , [ toList toSet]), collectAndThen,

复杂的归约,可以通过groupingBy以及partitioningBy完成,并且他们之间可以通过多个Collector的连接完成
如:

Map<Type, List<String>>  = 
   collect(groupingBy(Dish::getType,
                              mapping(Dish:getName, toList())));

或者多级分组

Map<Type, Map<CaloricLevel, List<Dish>>>  = 
.collect( groupingBy(Dish::getType,
                  groupingBy( dish -> {
                              if (dish.getCateGory <= 400 ) return CaloricLevel.DIET;
                              ......
                              })
                              ));

或者分组统计

Map<Dish.Type, Long>  = .collect( groupintBy(Dish::getType, counting() ));

或者分组统计后计算

Map<Dish.Type, Optional<Dish>> = .collect(groupingBy(Dish::getType, maxBy(comparingInt(Dish::getCalories))));

groupingBy(Dish::getType, collectAndThen( maxBy(comparingInt(Dish::getCalories)), Optional::get));

分区 partitioningBy

将流的元素,利用一个谓词做分类。分区可以理解为产生了两个流,并且由 partitioningBy实现的Map实现(特殊map)更高效,紧凑,因为只包含两个键: true 和 false

分区和分组一样,可以多级分区,只要 连接 partitioningBy 就可以

收集器性能对比

实现自定义的Collector的目的是为了获得 比 ’使用内置工厂方法创建的收集器‘ 更好的性能,但是也可能让性能更拉垮,只能说:提供了设置,让有能力的同学可以自由发挥。
JMH框架来测试。

并行

在Java8之前的并行流,需要主动的拆分数据,分配给不同的线程,然后还要进行协调和同步以避免可能发生的竞争条件,等到每个线程都完成后,再合并结果。并且java7引入了一个 “分支/合并”的框架,可以更稳定,更不容易出错的完成这一件事。
不过,相对于java8的并行流还是落后了些,Java8的Stream接口让用户免费使用并行,一个指令就可以将顺序流转化为并行流(当然,前提是你的数据能够接受并行处理),控制数据切分的过程主要是: Spliterator (splitable , iterator)

someStream.parallel() 方法并不会改变实际的流,而是设置了一个flag,表示 parallel之后的所有操作都是可以并行执行的,指需要再调用 sequential 就可以再变回顺序流。

并行流内部使用的还是:ForJoinPool,默认的个数等于cpu的个数。

java.util.concurrent.ForkJoinPool. common.parallelism 来修改线程池大小, 缺点是jvm级别的,会修改所有并行流的线程池大小,所以一般不修改,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");

并行流的思想是:数据分块,任务分块,然后利用Fork/Joinpool, 实现任务的计算。
任务的分块由Spliterator实现。

Fork/Join Pool框架

思想分治,将任务拆分,最后合并。

  • 可拆分的任务抽象ForkJoinTask<T> <= RecursiveTask<T>, 或者 ForkJoinTask<Void> <= RecursiveAction
    • 任务执行: compute, 任务在compute内部再次拆分,不能拆的计算然后返回结果,将之前的拆分都合并。
  • 执行任务的框架: work stealing。由于任务拆分的子任务的计算负载不均衡,导致虽然每个线程的队列都只有两个任务,但是其中一个队列的任务特别简单,瞬间完成,而另一个队列的任务可能特别耗时,第二个任务就在等待第一个完成,不能并发。steal能够让空闲/或者负载低的线程偷取忙碌线程的双端队列的task来帮助执行,这也是为什么 任务 要分细的原因。

流的拆分: Spliterator

参考:

interface Spliterator<T> {
  boolean tryAdvance(COnsumer< ? super T> action);

Spliterator<T> trySplit();

long estimateSize(); // 不是很准确

int characteristics();

}

trySplit会递归调用,调用的时候,它会划分一些元素给它返回的第二个Spliterator,让他们两个并行处理。

递归的拆分,拆分的过程会影响整个的执行效率,在自定义Spliterator的时候,注意效率,采用物理分割,比如将数据复制一份,显然没有逻辑分割保存原数据引用,修改数据范围来的效率高。另外,拆分的过程收到了 “特性”影响(characteristics方法声明)


image.png

如果当前的Spliterator实例X是可分割的,trySplit()方法会分割X产生一个全新的Spliterator实例Y,原来的X所包含的元素(范围)也会收缩,类似于X = [a,b,c,d] => X = [a,b], Y = [c,d];如果当前的Spliterator实例X是不可分割的,此方法会返回NULL),具体的分割算法由实现类决定

todo: 对于无限流的并发,是怎么split的?

集合与lambda带来的高效编程和改变

集合工厂的增强,对于List,Set,map等,SomeCollection.of() 方法会生成 小规模的高性能的不可变集合类,即集合常量 ,虽然生成的数据结构不可变,但是效率更高,就类似于: Arrays.asList() 方法一样,生成的是视图。
另外,对于大家常用集合类时候,用到的一些常用操作,都增加了相应语义的方法:

  • removeIf: list 和set提供,可以删除满足条件的元素,而不会触发ConcurrentModificationException,不然就要显式的使用迭代器和迭代器的删除方法;


    image.png

    正确的代码如下


    image.png
  • replaceAll: 替换集合的元素,而不用新生成集合。

  • remove: 删除map里面指定的 key和value 对

  • merge: 把两个map对元素进行合并的逻辑

  • computeIfXXXX,compute:高效的计算和填充

ConcurrentHashMap

  • set视图:一个可以时刻同步map的set视图,xxx.keySet()方法,随时在set里看到map的变化。
  • 基础类型的归约:使用对应的基础类型方法会更快,少去了装箱拆箱的步骤 : reduceValuesToInt、reduce-KeysToLong

lambda重构设计模式

新的语言特性常常让现存的编程模式或设计黯然失色,对设计经验的总结陈称为“设计模式”。
lambda之所以可以重构设计模式,是因为:将行为参数化,传递给高阶函数。而策略模式,模板模式的核心就是封装了不同的行为; 观察者模式,是在发生一些事件之后,触发一些行为的执行;责任链模式本质是对数据多次的操作,完全可以用函数式编程的组合模式完成;工厂模式,也只是某种特定的函数罢了:Function<someParameter, SomeClassInstance>;

基于lambda的DSL todo

精读此章节内容后,最好搭配:
英文版本的: Domain-Specific Languages Martin的书,中文翻译贼拉垮
DSLs in Action,有引进的话,先看中文看看
还有 antlr4的两本,因为 martin的书是设计思想,指导原则,用的工具还是 antlr4 做解析。

Optional,时间 : 非常简单,略

默认方法和模块系统

默认方法

模块系统

模块系统比较复杂: 搭配:Nicolai Parlog《 The Java Module System 》

设计的高层次(软件架构层次)设计模式:
关注点分离(separation of concern,SoC)和信息隐藏(information hiding)

  • 关注点分离 推崇的是将:单体的计算机程序分解为一个个相互独立的特性

采用关注点分离,可以将软件的功能,作用等划分到名为“模块“的独立组成部分中去,所以,模块是具有“内聚”特质的一组代码,它与其他模块的代码很少耦合;通过模块组织类,可以清晰地描绘出应用程序类与类之间的可见性关系。
Java的包机制并为从本质上支持模块化,它的粒度太粗。而模块化的粒度更细,且控制检查是编译期的。带来的好处就是:可以使得各项工作独立开展,减少组件之间的依赖,便于团队合作,有利于推动组建重用,系统整体的维护性更好。

  • 信息隐藏: 隐藏信息能够减少局部变更对其他部分程序的影响,从而避免“变更传递”。

在低层次(代码层次) 的表现就是封装。虽然我们具有private,protected,public 关键字,但是就语言层面而言,Java 9 出现之前, 编译器无法依据语言结构判断某个类或者包仅供某个特定目标访问。

Java9之前的Java内置模块化的问题:

  1. 有限的可见性控制:三个描述符只能控制包级别的类访问,无法描述 包之间的访问。比如:“希望一个包中的某个类或接口可以被另外一个包中的类或接口访问,那么只能将它声明为 public。这样一来,任何人都可以访问这些类和接口了”。这样就可能让代码被随意使用,给开发者演进自己的代码带来困难。
  2. 类路径的问题:Java编译器把所有的类都打入一个扁平的jar包中,并且把jar包放到class path上,jvm可以动态一句类的路径从中定位并且加载相关的类。然后,这样存在了几个严重的问题:
  • 无法通过路径指定版本。比如如果类路径上存在同一个库的两个版本,会发生什么。而大型项目中很常见,它的不同组件使用同一个库的不同版本:解决方法有
    1. 使用自定义ClassLoader来隔离: http://www.blogjava.net/landon/category/54860.html(值得注意的两点是:公有接口可以由系统类加载器加载,旧的类的实例和class很难被卸载)。比如: elasticsearch中的插件加载机制,实现了自定义的classLoader 。 甚至可以用ClassLoader来实现热部署:https://cloud.tencent.com/developer/article/1915650
    2. 或者是OSGI
    3. sofa-ark是动态热部署和类隔离框架,支付宝开源
  • 类路径不支持显式的依赖:


    image.png

Java9提供了一个新的单位: 模块。通过 module声明,紧接着的是模块的名字和主体的内容,定义在特殊的文件:module-info.class, 下图可知,module的层级是高于package的。

image.png

image.png

使用命令可以指导哪些目录和类文件会被打包进入生成的JAR文件中:
javac module-info.java xxx/yyyy/zzz.java -d target
jar cvfe xxxxx.jar xxx.yyy.zzz -C target
然后运行执行命令:
java --module-path xxxxx.jar --module moduleName.xxx.yyy.zzz

image.png

并发性

无论是基于 Future 的异步 API 还是反应式异步 API,被调方法的概念体(conceptual body) 都在另一个线程中执行,调用方很可能已经退出了执行,不在调用异常处理器的作用域内。很明显,这种非常规行为触发的异常需要通过其他的动作来处理。

CompletableFuture

和Scala的Future很类似

反应式编程

直接看《反应式设计模式》就好了

函数式

个人了解较多,忽略。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342