精华内容
下载资源
问答
  • 多线程模型中不包括
    2021-03-18 09:29:25

    Java多线程模型

    生命周期

    Java 线程的生命周期包括创建,就绪,运行,阻塞,死亡 5 个状态。一个 Java 线程总是处于这 5 个生命周期状态之一,并在一定条件下可以在不同状态之间进行转换 。

    线程的实现

    实现线程主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。

    线程调度

    线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive Threads-Scheduling)。

    虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。不过,线程优先级并不是太靠谱,原因是Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统。

    线程模型

    Future模型、Fork&Join 模型、Actor消息模型、生产者消费者模型、Master-Worker模型。

    Future模型

    Future模型通常在使用的时候需要结合Callable接口配合使用。Future:未来的、将来的,再结合Callable大概可以明白其功能。

    Future是把结果放在将来获取,当前主线程并不急于获取处理结果。允许子线程先进行处理一段时间,处理结束之后就把结果保存下来,当主线程需要使用的时候再向子线程索取。

    Callable是类似于Runnable的接口,其中call方法类似于run方法,所不同的是run方法不能抛出受检异常没有返回值,而call方法则可以抛出受检异常并可设置返回值。两者的方法体都是线程执行体。

    Fork&Join 模型

    该模型是jdk中提供的线程模型。该模型包含递归思想和回溯思想,递归用来拆分任务,回溯用合并结果。 可以用来处理一些可以进行拆分的大任务。

    其主要是把一个大任务逐级拆分为多个子任务,然后分别在子线程中执行,当每个子线程执行结束之后逐级回溯,返回结果进行汇总合并,最终得出想要的结果。这里模拟一个摘苹果的场景:有100棵苹果树,每棵苹果树有10个苹果,现在要把他们摘下来。为了节约时间,规定每个线程最多只能摘10棵苹树以便于节约时间。各个线程摘完之后汇总计算总苹果树。

    Actor消息模型

    actor模型属于一种基于消息传递机制并行任务处理思想,它以消息的形式来进行线程间数据传输,避免了全局变量的使用,进而避免了数据同步错误的隐患。actor在接受到消息之后可以自己进行处理,也可以继续传递(分发)给其它actor进行处理。在使用actor模型的时候需要使用第三方Akka提供的框架。

    生产者消费者模型

    生产者消费者模型都比较熟悉,其核心是使用一个缓存来保存任务。

    开启一个/多个线程来生产任务,然后再开启一个/多个来从缓存中取出任务进行处理。这样的好处是任务的生成和处理分隔开,生产者不需要处理任务,只负责向生成任务然后保存到缓存。而消费者只需要从缓存中取出任务进行处理。使用的时候可以根据任务的生成情况和处理情况开启不同的线程来处理。比如,生成的任务速度较快,那么就可以灵活的多开启几个消费者线程进行处理,这样就可以避免任务的处理响应缓慢的问题。

    Master-Worker模型

    master-worker模型类似于任务分发策略,开启一个master线程接收任务,然后在master中根据任务的具体情况进行分发给其它worker子线程,然后由子线程处理任务。如需返回结果,则worker处理结束之后把处理结果返回给master。下面的代码示例是使用akka actor for scala演示。使用的时候也可以使用java Thread来实现该模型。

    更多相关内容
  • Redis 6.0多线程模型总结

    千次阅读 多人点赞 2022-01-24 00:06:10
    这个版本提供了诸多令人心动的新特性及功能改进,比如新网络协议RESP3,新的集群代理,ACL等,其中关注度比较高的应该是多线程模型了。 1、Redis6.0之前的版本真的是单线程吗? Redis在处理客户端的请求时,包括...

    前言:Redis 6.0.1 于 2020 年 5 月 2 日正式发布了,如 Redis 作者 antirez 所说,这是迄今为止最“企业”化的版本,也是有史以来改动最大的一个 Redis 版本。这个版本提供了诸多令人心动的新特性及功能改进,比如新网络协议RESP3,新的集群代理,ACL等,其中关注度比较高的应该是多线程模型了。


    1、Redis6.0之前的版本真的是单线程吗?

    Redis在处理客户端的请求时,包括获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这就是所谓的“单线程”。但如果严格来讲从Redis4.0之后并不是单线程,除了主线程外,它也有后台线程在处理一些较为缓慢的操作,例如清理脏数据、无用连接的释放、大 key 的删除等等。

    其中执行命令阶段,由于 Redis 是单线程来处理命令的,所有每一条到达服务端的连接不会立刻执行,所有的连接都会进入一个 Socket 队列中,当 socket 可读则交给单线程事件分发器逐个被执行。如下图所示:


    2、Redis6.0之前为什么一直不使用多线程?

    官方曾做过类似问题的回复:使用Redis时,几乎不存在CPU成为瓶颈的情况, Redis主要受限于内存和网络。例如在一个普通的Linux系统上,Redis通过使用pipelining每秒可以处理100万个请求,所以如果应用程序主要使用O(N)或O(log(N))的命令,它几乎不会占用太多CPU。

    使用了单线程后,可维护性高。多线程模型虽然在某些方面表现优异,但是它却引入了程序执行顺序的不确定性,带来了并发读写的一系列问题,增加了系统复杂度、同时可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗。Redis通过AE事件模型以及IO多路复用等技术,处理性能非常高,因此没有必要使用多线程。单线程机制使得 Redis 内部实现的复杂度大大降低,Hash 的惰性 Rehash、Lpush 等等 “线程不安全” 的命令都可以无锁进行。

    为什么说Redis的瓶颈不在CPU?

    Redis绝大部分操作是基于内存的,而且是纯kv(key-value)操作,所以命令执行速度非常快。我们可以大概理解成,redis中的数据存储在一张大HashMap中,HashMap的优势就是查找和写入的时间复杂度都是O(1)。Redis内部采用这种结构存储数据,就奠定了Redis高性能的基础。根据Redis官网描述,在理想情况下Redis每秒可以提交一百万次请求,每次请求提交所需的时间在纳秒的时间量级。既然每次的Redis操作都这么快,单线程就可以完全搞定了,那还何必要用多线程呢!


    3、Redis6.0为什么要引入多线程呢?

    Redis将所有数据放在内存中,内存的响应时长大约为100纳秒,对于小数据包,Redis服务器可以处理80,000到100,000 QPS,这也是Redis处理的极限了,对于80%的公司来说,单线程的Redis已经足够使用了。

    但随着越来越复杂的业务场景,有些公司动不动就上亿的交易量,因此需要更大的QPS。常见的解决方案是在分布式架构中对数据进行分区并采用多个服务器,但该方案有非常大的缺点,例如要管理的Redis服务器太多,维护代价大;某些适用于单个Redis服务器的命令不适用于数据分区;数据分区无法解决热点读/写问题;数据偏斜,重新分配和放大/缩小变得更加复杂等等。

    从Redis自身角度来说,因为读写网络的read/write系统调用占用了Redis执行期间大部分CPU时间,瓶颈主要在于网络的 IO 消耗, 优化主要有两个方向:

    • 提高网络 IO 性能,典型的实现比如使用 DPDK 来替代内核网络栈的方式
    • 使用多线程充分利用多核,典型的实现比如 Memcached。

    协议栈优化的这种方式跟 Redis 关系不大,支持多线程是一种最有效最便捷的操作方式。所以总结起来,redis支持多线程主要就是两个原因:

    • 可以充分利用服务器 CPU 资源,目前主线程只能利用一个核
    • 多线程任务可以分摊 Redis 同步 IO 读写负荷


    4、Redis6.0默认是否开启了多线程?

    Redis6.0的多线程默认是禁用的,只使用主线程。如需开启需要修改redis.conf配置文件:io-threads-do-reads yes
    在这里插入图片描述


    5、Redis6.0多线程开启时,线程数如何设置?

    开启多线程后,还需要设置线程数,否则是不生效的。同样修改redis.conf配置文件
    在这里插入图片描述
    关于线程数的设置,官方有一个建议:4核的机器建议设置为2或3个线程,8核的建议设置为6个线程,线程数一定要小于机器核数。还需要注意的是,线程数并不是越大越好,官方认为超过了8个基本就没什么意义了。


    6、Redis6.0采用多线程后,性能的提升效果如何?

    Redis 作者 antirez 在 RedisConf 2019分享时曾提到:Redis 6 引入的多线程 IO 特性对性能提升至少是一倍以上。国内也有大牛曾使用unstable版本在阿里云esc进行过测试,GET/SET 命令在4线程 IO时性能相比单线程是几乎是翻倍了。

    测试环境

    Redis Server: 阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 内存,主机型号 ecs.ic5.2xlarge

    Redis Benchmark Client: 阿里云 Ubuntu 18.04,8 2.5 GHZ CPU, 8G 内存,主机型号 ecs.ic5.2xlarge

    测试结果
    在这里插入图片描述

    详见:正式支持多线程!Redis 6.0与老版性能对比评测 - 知乎

    说明1:这些性能验证的测试并没有针对严谨的延时控制和不同并发的场景进行压测。数据仅供验证参考而不能作为线上指标。

    说明2:如果开启多线程,至少要4核的机器,且Redis实例已经占用相当大的CPU耗时的时候才建议采用,否则使用多线程没有意义。所以估计80%的公司开发人员看看就好。


    7、Redis6.0多线程的实现机制?

     流程简述如下

    1、主线程负责接收建立连接请求,获取 socket 放入全局等待读处理队列
    2、主线程处理完读事件之后,通过 RR(Round Robin) 将这些连接分配给这些 IO 线程
    3、主线程阻塞等待 IO 线程读取 socket 完毕
    4、主线程通过单线程的方式执行请求命令,请求数据读取并解析完成,但并不执行
    5、主线程阻塞等待 IO 线程将数据回写 socket 完毕
    6、解除绑定,清空等待队列

     该设计有如下特点:

    • IO 线程要么同时在读 socket,要么同时在写,不会同时读或写

    • IO 线程只负责读写 socket 解析命令,不负责命令处理


    8、开启多线程后,是否会存在线程并发安全问题?

    从上面的实现机制可以看出,Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程顺序执行。所以我们不需要去考虑控制 key、lua、事务,LPUSH/LPOP 等等的并发及线程安全问题。


    9、Linux环境上如何安装Redis6.0.1(6.0的正式版是6.0.1)?

    这个和安装其他版本的redis没有任何区别,整个流程跑下来也没有任何的坑,所以这里就不做描述了。唯一要注意的就是配置多线程数一定要小于cpu的核心数,查看核心数量命令:

    [root@centos7.5 ~]# lscpu
    Architecture: x86_64
    CPU op-mode(s): 32-bit, 64-bit
    Byte Order: Little Endian
    CPU(s): 4
    On-line CPU(s) list: 0-3
    

    10、Redis作者是如何点评 “多线程”这个新特性的?

    关于多线程这个特性,在6.0 RC1时,Antirez曾做过说明:

    Redis支持多线程有2种可行的方式:第一种就是像“memcached”那样,一个Redis实例开启多个线程,从而提升GET/SET等简单命令中每秒可以执行的操作。这涉及到I/O、命令解析等多线程处理,因此,我们将其称之为“I/O threading”。另一种就是允许在不同的线程中执行耗时较慢的命令,以确保其它客户端不被阻塞,我们将这种线程模型称为“Slow commands threading”。

    经过深思熟虑,Redis不会采用“I/O threading”,redis在运行时主要受制于网络和内存,所以提升redis性能主要是通过在多个redis实例,特别是redis集群。接下来我们主要会考虑改进两个方面:

    • Redis集群的多个实例通过编排能够合理地使用本地实例的磁盘,避免同时重写AOF。

    • 提供一个Redis集群代理,便于用户在没有较好的集群协议客户端时抽象出一个集群。

    补充说明一下,Redis和memcached一样是一个内存系统,但不同于Memcached。多线程是复杂的,必须考虑使用简单的数据模型,执行LPUSH的线程需要服务其他执行LPOP的线程。

    我真正期望的实际是“slow operations threading”,在redis6或redis7中,将提供“key-level locking”,使得线程可以完全获得对键的控制以处理缓慢的操作。

    详见:An update about Redis developments in 2019 - <antirez>


    11、Redis线程中经常提到IO多路复用,如何理解?

    这是IO模型的一种,即经典的Reactor设计模式,有时也称为异步阻塞IO。
    在这里插入图片描述

    多路指的是多个socket连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll是最新的也是目前最好的多路复用技术。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求,且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。


    12、Redis多线程模型缺陷

    随着互联网的飞速发展,互联网业务系统所要处理的线上流量越来越大,Redis 的单线程模式会导致系统消耗很多 CPU 时间在网络 I/O 上从而降低吞吐量,于是诞生了Redis6.0多线程模型。

    但是 Redis 多线程网络模型实际上并不是一个标准的 Multi-Reactors/Master-Workers模型。Redis 的多线程方案中,I/O 线程任务仅仅是通过 socket 读取客户端请求命令并解析,却没有真正去执行命令。所有客户端命令最后还需要回到主线程去执行,因此对多核的利用率并不算高,而且每次主线程都必须在分配完任务之后忙轮询等待所有 I/O 线程完成任务之后才能继续执行其他逻辑。

    在我看来,Redis 目前的多线程方案更像是一个折中的选择:既保持了原系统的兼容性,又能利用多核提升 I/O 性能。


    参考链接:

    Redis 6.0 新特性-多线程连环13问! - 牧码哥 - 博客园

    Redis多线程演进

    Redis6.0为何引入多线程?单线程不香吗?

    展开全文
  • Java 线程模型

    千次阅读 2021-02-12 22:24:09
    多线程模型实现多线程主要有3种模型:内核线程模型、用户线程模型、混合线程模型内核线程模型内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程。在此模型下,线程的切换...

    线程是CPU调度执行的基本单位,多个线程共享系统为进程分配的资源,又可以被系统独立调度执行。

    多线程模型

    实现多线程主要有3种模型:内核线程模型、用户线程模型、混合线程模型

    内核线程模型

    内核线程模型即完全依赖操作系统内核提供的内核线程(Kernel-Level Thread ,KLT)来实现多线程。在此模型下,线程的切换调度由系统内核完成,系统内核负责将多个线程执行的任务映射到各个CPU中去执行。

    b1bbc9bc78b2fb060bdfc244057fcf3c.png

    用户进程使用系统内核提供的接口———轻量级进程(Light Weight Process,LWP)来使用系统内核线程。

    在此种线程模型下,由于一个用户线程对应一个LWP,因此某个LWP在调用过程中阻塞了不会影响整个进程的执行。

    但这种线程模型也有如下缺点:

    各种线程的操作都需要在用户态和内核态之间频繁切换,消耗较大

    每个LWP都需要一个内核线程来支持执行用户代码,会消耗更多的内核内存空间,因此系统内核提供的KLT的数量是有限制的。

    - **用户线程模型**

    用户线程模型完全建立在用户空间的线程库上,不依赖于系统内核,用户线程的创建、同步、切换和销毁等操作完全在用户态执行,不需要切换到内核态。

    8a56b37ab9d3b4bd7504a47ae12abd5e.png

    在此种线程模型下,线程的各种操作以及切换消耗很低,但线程的所有操作都需要在用户态实现,线程的调度实现起来异常复杂,处理器映射更是无法实现。

    - **混合线程模型**

    混合线程模型是前述两种模型的混合版本,用户线程仍然是在用户态中创建,用户线程的创建、切换和销毁的消耗很低,用户线程的数量不受限制。而LWP在用户线程和内核线程之间充当桥梁,就可以使用操作系统提供的线程调度和处理器映射功能。

    7076b7dc7711ca16f0c8e4e858025bdf.png

    当前,Java虚拟机使用的线程模型是基于操作系统提供的原生线程模型来实现的,Windows系统和Linux系统都是使用的内核线程模型,而Solaris系统支持混合线程模型和内核线程模型两种实现。

    Java线程内存模型

    Java线程内存模型中,可以将虚拟机内存划分为两部分内存:主内存和线程工作内存,主内存是多个线程共享的内存,线程工作内存是每个线程独享的内存。

    c688d33037980fedc702b2e6661b6eb9.png

    在上图中,方法区和堆内存就是主内存区域,而虚拟机栈、本地方法栈以及程序计数器则属于每个线程独享的工作内存。

    Java内存模型规定所有成员变量都需要存储在主内存中,线程会在其工作内存中保存需要使用的成员变量的拷贝,线程对成员变量的操作(读取和赋值等)都是对其工作内存中的拷贝进行操作。

    各个线程之间不能互相访问工作内存,线程间变量的传递需要通过主内存来完成。

    02b39688b0a70075dacedc25e895b910.png

    Java内存模型定义了8种原子操作来实现上图中的线程内存交互:

    read,将主内存中的一个变量的值读取出来

    load,将read操作读取的变量值存储到工作内存的副本中

    use,把工作内存中的变量的值传递给执行引擎

    assign,把从执行引擎中接收的值赋值给工作内存中的变量

    store,把工作内存中一个变量的值传递到主内存

    write,将store操作传递的值写入到主内存的变量中

    lock,将主内存中的一个变量标识为某个线程独占的锁定状态

    unlock,将主内存中线程独占的一个变量从锁定状态中释放

    ####原子性、可见性和有序性

    - **原子性**

    Java内存模型定义了8中原子操作,此外Java内存模型还保证了对于基本数据类型(char、boolean、int等)的操作是原子性的。对于其他类型的数据如若需要更灵活的原子性操作,Java内存模型提供了lock和unlock操作。JVM中使用的两个字节码指令monitorenter和monitorexit即是通过lock和unlock操作来实现的,常使用的synchronized关键字转换成字节码指令后即由monitorenter和monitorexit构成。

    可见性

    可见性是指当一个线程修改了主内存中变量的值,其他线程可以立即获取这个修改后的新值。只要在工作内存中修改变量之后立即存储到主内存,以及读取一个变量之前强制从主内存刷新变量的值即可保证可见性。volatile关键字即通过上述方法保证多线程操作变量时的可见性。

    有序性

    有序性是指在同一个线程中的所有操作都是有序执行的,但由于指令重排序等行为会导致指令执行的顺序不一定是按照代码中的先后顺序执行的,在多线程中对一个变量的操作就可能会受到指令重排序的影响。volatile关键字包含有禁止指令重排序的作用,因此使用volatile关键字修饰的变量可以保证多线程之间对该变量操作的有序性。

    参考资料:《深入理解Java虚拟机》

    展开全文
  • 在处理业务的时候,有时候需要根据情况使用不同的线程处理模型来处理业务逻辑,这里演示一下常见的线程模型使用技巧。 1、Future模型  前面的章节提到过Future模型,该模型通常在使用的时候需要结合Callable...

    在处理业务的时候,有时候需要根据情况使用不同的线程处理模型来处理业务逻辑,这里演示一下常见的线程模型使用技巧。

    1、Future模型

      前面的章节中提到过Future模型,该模型通常在使用的时候需要结合Callable接口配合使用。Future:未来的、将来的,再结合Callable大概可以明白其功能。

      Future是把结果放在将来获取,当前主线程并不急于获取处理结果。允许子线程先进行处理一段时间,处理结束之后就把结果保存下来,当主线程需要使用的时候再向子线程索取。

      Callable是类似于Runnable的接口,其中call方法类似于run方法,所不同的是run方法不能抛出受检异常没有返回值,而call方法则可以抛出受检异常并可设置返回值。两者的方法体都是线程执行体。

    /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;

    注意这里,无法抛出受检异常不等于无法捕获线程中throws的异常。run方法执行体中抛出异常是可以被捕获的,前提是使用Future来处理,后面会有说明。

           如果有一种场景需要一个线程处理一段业务,处理结束之后主线程将会使用处理结果进行后续处理。这样,按照普通逻辑,就需要使用到一个全局变量来保存子线程处理之后的结果。子线程处理结束之后,把结果保存在全局变量中供主线程进行调用。一旦涉及到全局能量便存在着多线程读写全局变量错误的风险。而使用Future模式便可以省去全局变量的使用,直接从线程中获取子线程处理结果。下面看一下使用示例;

    package thread.blogs.threadmodel;
    
    /**
     * Created by PerkinsZhu on 2017/9/1 15:34.
     */
    public class AbstractModel {
        protected static void sleep(int time) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        protected static void println(Object info) {
            System.out.println(info);
        }
    }
    package thread.blogs.threadmodel;
    
    import java.util.concurrent.*;
    
    /**
     * Created by PerkinsZhu on 2017/9/1 15:32.
     */
    public class FutureModel extends AbstractModel {
        public static void main(String[] args) {
            testFuture();
        }
    
        /**
         * 区别: CallAble  可以有返回值  可以抛出受检异常
         * Runnable  没有返回值   无法抛出受检异常但可捕获线程中发生的异常。
         * 者都可通过对future.get()进行try cathch捕获异常
         */
        private static void testFuture() {
            MyCallable myCallable = new MyCallable();
            MyRunnable myRunnable = new MyRunnable();
            ExecutorService executorService = Executors.newFixedThreadPool(5);
            Future<?> future = executorService.submit(myCallable);
            sleep(2000);
            try {
                //String data = future.get(2000, TimeUnit.MILLISECONDS);//可以指定超时时间
                Object data = future.get();//当执行Runnable的时候,这里返回的为nul。此时如果有run方法体中有异常异常抛出,可以在此捕获到,虽然Run方法没有显示的抛出受检异常。
                println(data + "---" + data.getClass().toString());
            } catch (InterruptedException e) {
                println(e.getMessage());
            } catch (ExecutionException e) {
                println(e.getMessage());
            } catch (Exception e) {
                println(e.getMessage());
            }
            executorService.shutdown();
        }
    
        static class MyCallable implements Callable<String> {
            @Override
            public String call() throws Exception {
                sleep(500);
                println("I am Callable...");
                //int num = 10/0;
                //throw  new RuntimeException("异常");
                return "hello";
            }
        }
    
        static class MyRunnable implements Runnable {
            @Override
            public void run() {//不支持返回值,无法对线程捕获异常。
                sleep(500);
                println("I am Runnable...");
                // int num = 10/0;
                //throw  new RuntimeException("异常");
            }
        }
    }

     可以取消注释 分别测试 myCallable  和myRunnable 对异常捕获和结果获取进行测试。

    2、fork&join 模型
            该模型是jdk中提供的线程模型。该模型包含递归思想和回溯思想,递归用来拆分任务,回溯用合并结果。 可以用来处理一些可以进行拆分的大任务。其主要是把一个大任务逐级拆分为多个子任务,然后分别在子线程中执行,当每个子线程执行结束之后逐级回溯,返回结果进行汇总合并,最终得出想要的结果。这里模拟一个摘苹果的场景:有100棵苹果树,每棵苹果树有10个苹果,现在要把他们摘下来。为了节约时间,规定每个线程最多只能摘10棵苹树以便于节约时间。各个线程摘完之后汇总计算总苹果树。代码实现如下:

    package thread.blogs.threadmodel;
    
    import scala.Console;
    
    import java.util.concurrent.*;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * Created by PerkinsZhu on 2017/9/5 13:05.
     */
    public class ForkJoin {
        public static void main(String[] args) {
            testAcation();
        }
    
        private static void testAcation() {
            ForkJoinPool pool = new ForkJoinPool();
            ForkJoinTask<Integer> future = pool.submit(new ResultTask(100));//共100棵苹果树
            try {
                Console.println(future.get());
                pool.awaitTermination(1000, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                e.printStackTrace();
            }
            pool.shutdown();
        }
    }
    
    class ResultTask extends RecursiveTask<Integer> { //也可继承自RecursiveAction抽象类,区别在于compute方法没有返回值,如果只需要执行动作则可以使用该接口
        private int treeNum;
    
        public ResultTask(int num) {
            this.treeNum = num;
        }
    
        @Override
        protected Integer compute() {
            if (treeNum < 10) {//每个线程最多只能摘10棵苹果树
                return getAppleNum(treeNum);
            } else {
    
                //对任务进行拆分,注意这里不仅仅可以一分为二进行拆分,也可以拆为多个子任务
                int temp = treeNum / 2;
                ResultTask left = new ResultTask(temp);
                ResultTask right = new ResultTask(treeNum - temp);
                left.fork();
                right.fork();
                //对子任务处理的结果进行合并
                int result = left.join() + right.join();
    
                Console.println("========" + Thread.currentThread().getName() + "=========" + result);
                return result;
            }
        }
    
        public Integer getAppleNum(int treeNum) {
            return treeNum * 10;//每棵树上10个苹果
        }
    }

    这里需要看一下执行结果,主要是为了明白在拆分子任务的时候并不是无限制开启线程,而是使用了线程池ForkJoinPool复用线程。注意下面输出的线程名称!

    ========ForkJoinPool-1-worker-3=========120
    ========ForkJoinPool-1-worker-7=========120
    ========ForkJoinPool-1-worker-0=========120
    ========ForkJoinPool-1-worker-5=========120
    ========ForkJoinPool-1-worker-1=========130
    ========ForkJoinPool-1-worker-11=========130
    ========ForkJoinPool-1-worker-4=========250
    ========ForkJoinPool-1-worker-7=========130
    ========ForkJoinPool-1-worker-7=========250
    ========ForkJoinPool-1-worker-3=========130
    ========ForkJoinPool-1-worker-5=========250
    ========ForkJoinPool-1-worker-6=========250
    ========ForkJoinPool-1-worker-2=========500
    ========ForkJoinPool-1-worker-3=========500
    ========ForkJoinPool-1-worker-1=========1000
    1000

    3、actor消息模型

      actor模型属于一种基于消息传递机制并行任务处理思想,它以消息的形式来进行线程间数据传输,避免了全局变量的使用,进而避免了数据同步错误的隐患。actor在接受到消息之后可以自己进行处理,也可以继续传递(分发)给其它actor进行处理。在使用actor模型的时候需要使用第三方Akka提供的框架点击查看

    4、生产者消费者模型

      生产者消费者模型都比较熟悉,其核心是使用一个缓存来保存任务。开启一个/多个线程来生产任务,然后再开启一个/多个来从缓存中取出任务进行处理。这样的好处是任务的生成和处理分隔开,生产者不需要处理任务,只负责向生成任务然后保存到缓存。而消费者只需要从缓存中取出任务进行处理。使用的时候可以根据任务的生成情况和处理情况开启不同的线程来处理。比如,生成的任务速度较快,那么就可以灵活的多开启几个消费者线程进行处理,这样就可以避免任务的处理响应缓慢的问题。使用示例如下:

    package thread.blogs.threadmodel;
    
    import java.util.LinkedList;
    import java.util.Queue;
    import java.util.UUID;
    
    /**
     * Created by PerkinsZhu on 2017/9/22 8:58.
     */
    public class PCModel {
        public static void main(String[] args) {
            testPCModel();
        }
        private static Queue<String> queue = new LinkedList<String>();//任务缓存,这里保存简单的字符串模拟任务
        private static void testPCModel() {
            new Thread(() -> {//生产者线程
                while (true) {
                    String uuid = UUID.randomUUID().toString();
                    queue.add(uuid);
                    sleep(100);
                }
            }).start();
            for (int i = 0; i < 10; i++) {//开启10消费者处理任务,保证生产者产生的任务能够被及时处理
                new Thread(() -> {
                    while (true) {
                        doWork(queue);
                    }
                }).start();
            }
        }
    
        private static void doWork(Queue<String> queue) {
            sleep(1000);
            synchronized (queue) {
                if (queue.size() > 0) {
                    sleep(10);
                    System.out.println(queue.poll() + "----" + queue.size());
                }
            }
        }
        private static void sleep(int time) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    5、master-worker模型

      master-worker模型类似于任务分发策略,开启一个master线程接收任务,然后在master中根据任务的具体情况进行分发给其它worker子线程,然后由子线程处理任务。如需返回结果,则worker处理结束之后把处理结果返回给master。下面的代码示例是使用akka actor for scala演示。使用的时候也可以使用java Thread来实现该模型。

    package thread.blogs.threadmodel
    
    import akka.actor.{Actor, ActorSystem, Props}
    
    /**
      * Created by PerkinsZhu on 2017/9/21 18:58. 
      */
    object ActorTest {
      val actorSystem = ActorSystem("Master")
    
    
      def main(args: Array[String]): Unit = {
        val actor = actorSystem.actorOf(Props[Master], "Master")
        var taskNum = 0;
        while (true) {
          taskNum = taskNum + 1;
          actor ! Task("做作业!  --" + taskNum) //发送消息给actor
          Thread.sleep(100)
        }
      }
    }
    
    class Master extends Actor {
      val actorSystem = ActorSystem("worker")
      var num = 0;
    
      override def receive: Receive = {
        case task: Task => {
          num = num + 1;
          //接收到任务之后分发给其它worker线程。可以使用worker池 复用actor
          actorSystem.actorOf(Props[Worker], "worker" + num) ! task
        }
        case any: Any => println(any)
      }
    }
    
    class Worker extends Actor {
    
      def doWork(task: Task): Unit = println(task.name)
    
      override def receive: Receive = {
        case task: Task => doWork(task) //worker处理接受到的任务
        case any: Any => println(any)
      }
    }
    
    case class Task(name: String)

     这里如果需要worker返回处理结果,则只需要在worker中调用sender 发送处理结果即可。

       

     

    展开全文
  • Reactor 线程模型

    千次阅读 2021-03-09 22:58:21
    线程模型 不同的线程模型,对程序段的影响很大,先来看看各个线程模型。 目前存在的线程模型有: 传统阻塞I/O服务模型 Reactor模型 根据 Reactor 的数量和处理资源池线程的数量不同,有3种典型的实现: 单Reactor...
  • 目录基本概念线程模型多对一模型一对一模型模型并发上的区别 参考文章 线程的3种实现方式–内核级线程, 用户级线程和混合型线程 Chapter 2 Multithreading Operating System Concepts 基本概念 用户级...
  • 迄今为止,我们只是泛泛地讨论了线程。不过,有两种不同方法来提供线程支持:用户层的用户线程或内核层的内核线程。 用户线程位于内核之上,它的管理无需内核支持;...对一模型(图 1)映射个用户级线程到一
  • dpdk多线程模型

    千次阅读 2019-09-07 15:16:52
    一、多线程模型 一个cpu上可以运行多个线程, 由linux内核来调度各个线程的执行。内核在调度线程时,会进行上下文切换,保存线程的堆栈等信息, 以便这个线程下次再被调度执行时,继续从指定的位置开始执行。然而...
  • 解析Netty三种线程模型

    千次阅读 2020-04-20 22:09:01
    Reactor模式是基于事件驱动开发的,核心组成部分包括Reactor和线程池,其中Reactor负责监听和分配事件,线程池负责处理事件。 Netty是基于Reactor模型开发的,在netty...单 Reactor 单线程模型如下图所示: 或者...
  • Java多线程基础——线程模型

    千次阅读 2018-04-23 17:54:16
    又由于线程间需要交互信息,在多线程环境,需要做好同步操作,以防止可预期的错误发生。因此,掌握多线程相关知识对于开发尤为重要。比如在我们常用的Okhttp,Rxjava等框架中都可以看见多线程的身影...
  • 主要介绍了简单了解Java Netty Reactor三种线程模型,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 最近想用PyQt5完成图像识别的一个GUI系统,在调用算法模型进行识别的时候,界面会卡住没有反应,所以想学习一下多线程解决这个问题。然后。。。发现没有基础学习来确实挺难,幸运地是最终实现了多线程,记录一下学习...
  • Reactor三种线程模型与Netty线程模型

    千次阅读 2019-06-27 22:53:37
    一、Reactor三种线程模型 1.1、单线程模型 单个线程以非阻塞IO或事件IO处理所有IO事件,包括连接、读、写、异常、关闭等等。单线程Reactor模型基于同步事件分离器来分发事件,这个同步事件分离器,可以看做是一个...
  • 使用python多线程加载模型并测试

    千次阅读 2019-11-14 15:43:46
    但是现在需要处理比较大量的数据,如果进行线性处理,可能测试一次就花10个小时,而且还知道结果怎么样,所以多线程就必须使用上了。有关线程部分主要参考:https://junyiseo.com/python/211.html 1 多线程 ...
  • 多线程模型

    千次阅读 2018-02-07 16:45:06
    两种常用的线程模型 生产者-消费者模型 就是由一个线程生产任务,而另外一个线程执行任务,二个线程之间有一个共享数据区,这种数据结构可以用队列来表示,但是必须是并发同步的,也就是就共享数据...
  • Redis单线程模型详解

    千次阅读 2022-01-16 16:28:42
    这里写目录标题Redis 单线程模型简介文件事件常用的文件时间处理器客户端与Redis通信的一次流程Redis 为什么要引入多线程呢?为什么Redis单线程模型也能效率这么高? Redis 单线程模型简介 Redis 内部使用文件事件...
  • OSG 高效渲染 之 多线程模型与渲染

    千次阅读 2019-10-28 09:27:54
    这里以OSG多线程渲染为例,谈谈OSG渲染引擎多线程渲染模式,再说说哪里有点“过时”了需要改进。 先谈点题外话,曾经看到知乎上一篇关于游戏引擎的设计讨论的文章,有位“大大”提出游戏引擎的设计关键在于架构设计...
  • Redis 多线程网络模型全面揭秘

    千次阅读 多人点赞 2021-02-21 15:31:18
    导语一文带你完全吃透 Redis ...Redis 多线程网络模型 设计思路 源码剖析 性能提升 模型缺陷 总结 博客原文 导言 在目前的技术选型,Redis 俨然已经成为了系统高性能缓存方案的事实标准,因此现在 Redis...
  • - 进程与线程 - 引入线程后的变化 - 线程的属性 - 线程的三种实现方式 - 三种多线程模型的定义与优缺点
  • 文章目录 现代操作系统三种线程模型 线程库实现 问题汇总 现代操作系统三种线程模型 首先我们需要了解线程(threads)是个什么概念。在传统UNIX,进程(process,就是Intel所谓的task)是调度的最小单位,复杂的大型...
  • 大致内容包括: Java内存模型 Java内存交互协议 Java的线程 Netty的并发编程分析 正确的使用锁 volatile的正确使用 CAS指令和原子类 线程安全类 读写锁的应用
  • 笔者看来Netty的内核主要包括如下图三个部分: 其各个核心模块主要的职责如下: 内存管理 主要提高高效的内存管理,包含内存分配,内存回收。...线程模型在网络通信主要解决什么样的问题?在 Nett
  • 多线程-内存模型

    千次阅读 2018-06-01 18:30:21
    内存模型是深入了解多线程开发的基石 1.多线程起源 2.内存模型基础–硬件优化 3.内存模型详细说明 4.原子性 5.有序性 6.可见性 7.先行发生规则 1.多线程起源 计算机运行速度快,但是存储和通信子系统...
  • 彻底搞懂Netty的线程模型

    千次阅读 2022-03-01 22:37:28
    要学习netty线程模型,先了解三种IO模型,这样理解netty线程模型和IO模型更轻松。 BIO 同步阻塞IO模型 一个线程负责连接,多线程则为每一个接入开启一个线程一个请求一个应答请求之后应答之前客户端会一直阻塞 NIO...
  • 线程模型 Dart 在单线程是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。从图可以发现,微任务队列的执行优先级高于事件队列。 ...
  • 多线程的几种模型

    千次阅读 2017-01-05 00:15:54
    多线程的几种模型1.对多线程的理解多线程在iOS中用的很多,比如每个asi请求,sdwebimage请求,数据请求,等待网络数据处理,多线程/异步就是为了界面流畅,防止假死, 每一个ASI请求就是一个NSOperation 每一个...
  • Redis单线程模型

    千次阅读 2019-09-18 21:58:16
    1.Redis单线程模型 1.1.文件事件处理器 1>.Redis基于Reactor模式开发了网络事件处理器,这个处理器就叫做文件事件处理器(file event handler).这个文件事件处理器是单线程的,所以Redis才叫做单线程的模型,文件事件...
  • 多线程常用的线程模型

    千次阅读 2018-06-03 23:31:15
    1.Futura模型 Future模型通常在使用的时候需要结合Callable接口配合使用,Future模型即是把线程的执行结果放到将来获取。当前主线程并急于获取处理结果时,允许子线程先进行处理一段时间,处理结束之后就把结果...
  • 本文通过与传统I/O模型以及三种Reactor模型进行对比,详细分析了Netty的线程模型是怎么设计的,以及它的工作流程

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 364,714
精华内容 145,885
关键字:

多线程模型中不包括