【小家java】Stream流操作的有状态 vs 无状态(上)

简介: 【小家java】Stream流操作的有状态 vs 无状态(上)

概念解释


说这个命题之前,我先解释一下编程里,有状态和无状态都什么意思


有状态


       有状态就是有数据存储功能,线程不安全


无状态


        无状态就是一次操作,不能保存数据。线程安全


下面我们先看看Srping中的Bean来辅助理解:

Spring中的有状态(Stateful)和无状态(Stateless)


     1.无状态的Bean适合用不变模式,技术就是单例模式,这样可以共享实例,提高性能。有状态的Bean,多线程环境下不安全,那么适合用Prototype原型模式。Prototype: 每次对bean的请求都会创建一个新的bean实例。


     2.默认情况下,从Spring bean工厂所取得的实例为singleton(scope属性为singleton),容器只存在一个共享的bean实例


    3.Service层、Dao层用默认singleton就行,虽然Service类也有dao这样的属性,但dao这些类都是没有状态信息的,也就是相当于不变(immutable)类,所以不影响。Struts2中的Action因为会有User、BizEntity这样的实例对象,是有状态信息的,在多线程环境下是不安全的,所以Struts2默认的实现是Prototype模式。在Spring中,Struts2的Action中,scope要配成prototype作用域


Servlet、Out,Request,Response,Session,Config,Page,PageContext是线程安全的,Application在整个系统内被使用,所以不是线程安全的.


Stream流操作的有状态 vs 无状态


比如map或者filter会从输入流中获取每一个元素,并且在输出流中得到一个结果,这些操作没有内部状态,称为无状态操作。


但是像reduce、sum、max这些操作都需要内部状态来累计计算结果,所以称为有状态操作。


这里需要单独解释一下:


有一些操作sort、distinct、limit、skip看上去和filter、map差不多,他们接收一个流,再生成一个流,但是区别在于排序和去重复项需要知道先前的历史。比如排序就需要将所有元素放入缓存区后才能给输出流加入一个项目,这个操作对缓存的要求是无上限的,流有多大就需要多大的缓存才能进行运算。这些操作也是有状态操作。


image.png


所以判断流操作是否有状态的判断标准,就是看是否需要知道先前的数据历史。前后数据是否有依赖关系来判断


中间操作就像是水管的一部分,终端操作就像水龙头,增加水管长度不会消耗水,只有打开水龙头才会开始消耗水。


peek方法一般在debug的时候才会开启


下面举个例子,体验一把有状态和无状态:

String str = "my name is fangshixiang";
        Stream.of(str.split(" ")).peek(x -> System.out.println(Thread.currentThread().getName() + "___" + x))
                .map(x -> x.length()).count();
输出:
main___my
main___name
main___is
main___fangshixiang


我们发现,打印的线程都是main主线程,因为我们是串行流嘛。

现在我们试试并行流打印输出:


public static void main(String[] args) {
        //打印每个单词的长度
        String str = "my name is fangshixiang";
        Stream.of(str.split(" ")).parallel().peek(x -> System.out.println(Thread.currentThread().getName() + "___" + x))
                .map(x -> x.length()).count();
    }
输出:
ForkJoinPool.commonPool-worker-1___fangshixiang
main___is
ForkJoinPool.commonPool-worker-2___name
ForkJoinPool.commonPool-worker-3___my

看出效果了没?并行流输出无顺序。并行流采用的ForkJoin框架的线程池ForkJoinPool.commonPool。所有的并行流都会使用同一个线程池,所以如果并行流太多的话,也会出现阻塞的。因此若需要,我们可以让它使用我们自己的线程池,来提高效率。


备注ForkJoinPool.commonPool的线程池大小默认为CPU核心数量。当然可以通脱System.setProperty手动修改这个值,只是一般都没有需要


相关文章
|
25天前
|
存储 Java API
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
213 92
|
25天前
|
存储 Java API
Java Stream API:现代数据处理之道
Java Stream API:现代数据处理之道
150 68
|
2月前
|
Oracle Java 关系型数据库
掌握Java Stream API:高效集合处理的利器
掌握Java Stream API:高效集合处理的利器
334 80
|
2月前
|
安全 Java API
Java 8 Stream API:高效集合处理的利器
Java 8 Stream API:高效集合处理的利器
222 83
|
3月前
|
存储 Java 大数据
Java代码优化:for、foreach、stream使用法则与性能比较
总结起来,for、foreach和stream各自都有其适用性和优势,在面对不同的情况时,有意识的选择更合适的工具,能帮助我们更好的解决问题。记住,没有哪个方法在所有情况下都是最优的,关键在于理解它们各自的特性和适用场景。
371 23
|
3月前
|
SQL JSON 安全
Java 8 + 中 Lambda 表达式与 Stream API 的应用解析
摘要:本文介绍了Java 8+核心新特性,包括Lambda表达式与Stream API的集合操作(如过滤统计)、函数式接口的自定义实现、Optional类的空值安全处理、接口默认方法与静态方法的扩展能力,以及Java 9模块化系统的组件管理。每个特性均配有典型应用场景和代码示例,如使用Stream统计字符串长度、Optional处理Map取值、模块化项目的依赖声明等,帮助开发者掌握现代Java的高效编程范式。(150字)
66 1
|
2月前
|
SQL 人工智能 Rust
Java 开发中Stream的toMap与Map 使用技巧
本文深入解析了 Java 中 `toMap()` 方法的三大问题:重复键抛出异常、`null` 值带来的风险以及并行流中的性能陷阱,并提供了多种替代方案,如使用 `groupingBy`、`toConcurrentMap` 及自定义收集器,帮助开发者更安全高效地进行数据处理。
162 0
|
2月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
125 0
|
2月前
|
Java API 调度
从阻塞到畅通:Java虚拟线程开启并发新纪元
从阻塞到畅通:Java虚拟线程开启并发新纪元
282 83