【小家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手动修改这个值,只是一般都没有需要


相关文章
|
3月前
|
存储 缓存 安全
HashMap VS TreeMap:谁才是Java Map界的王者?
HashMap VS TreeMap:谁才是Java Map界的王者?
160 2
|
29天前
|
存储 缓存 Oracle
Java线程池,白话文vs八股文,原来是这么回事!
本文介绍了Java线程池的原理、实现方式及相关参数。首先,通过类比公司员工的方式解释了线程池的核心概念,如核心线程、最大线程数、任务队列和拒绝策略。接着,详细描述了线程池的任务处理流程,并提供了使用`ThreadPoolExecutor`和`Executors`创建线程池的代码示例,强调了`ThreadPoolExecutor`的灵活性和`Executors`的局限性。最后,总结了线程池的相关参数及不同类型的线程池实现,并附带常见面试题及其解答,帮助读者全面理解线程池的应用场景和优化方法。
47 4
|
1月前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
82 6
|
1月前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
1月前
|
Rust 安全 Java
Java Stream 使用指南
本文介绍了Java中Stream流的使用方法,包括如何创建Stream流、中间操作(如map、filter、sorted等)和终结操作(如collect、forEach等)。此外,还讲解了并行流的概念及其可能带来的线程安全问题,并给出了示例代码。
|
2月前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
35 0
|
2月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
3月前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
147 3
|
3月前
|
Java API 数据处理
java Stream详解
【10月更文挑战第4天】
52 0
|
16天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
74 17