Netty In Action中文版 - 第十五章:选择正确的线程模型
http://blog.csdn.net/abc_key/article/details/38419469
本章介绍
- 线程模型(thread-model)
- 事件循环(EventLoop)
- 并发(Concurrency)
- 任务运行(task execution)
- 任务调度(task scheduling)
线程模型定义了应用程序或框架怎样运行你的代码。选择应用程序/框架的正确的线程模型是非常重要的。Netty提供了一个简单强大的线程模型来帮助我们简化代码,Netty对全部的核心代码都进行了同步。全部ChannelHandler,包含业务逻辑。都保证由一个线程同一时候运行特定的通道。
这并不意味着Netty不能使用多线程,仅仅是Netty限制每一个连接都由一个线程处理。这样的设计适用于非堵塞程序。我们没有必要去考虑多线程中的不论什么问题,也不用操心会抛ConcurrentModificationException或其它一些问题,如数据冗余、加锁等,这些问题在使用其它框架进行开发时是常常会发生的。
读完本章就会深刻理解Netty的线程模型以及Netty团队为什么会选择这种线程模型。这些信息能够让我们在使用Netty时让程序由最好的性能。
此外,Netty提供的线程模型还能够让我们编写整洁简单的代码,以保持代码的整洁性;我们还会学习Netty团队的经验,过去使用其它的线程模型,如今我们将使用Netty提供的更easy更强大的线程模型来开发。
虽然本章讲述的是Netty的线程模型,可是我们仍然能够使用其它的线程模型。至于怎样选择一个完美的线程模型应该依据应用程序的实际需求来推断。
本章如果例如以下:
- 你明确线程是什么以及怎样使用,并有使用线程的工作经验;若不是这样,就请花些时间来了解清楚这些知识。
推荐一本书:Java并发编程实战。
- 你了解多线程应用程序及其设计,也包含怎样保证线程安全和获取最佳性能。
- 你了解java.util.concurrent以及ExecutorService和ScheduledExecutorService。
15.1 线程模型概述
比如,你有一个餐厅。向你的客户提供食品,食物须要在厨房煮熟后才干给客户;某个客户下了订单后,你须要将煮熟事物这个任务发送到厨房,而厨房能够以不同的方式来处理,这就像一个线程模型,定义了怎样运行任务。
- 仅仅有一个厨师:
- 这样的方法是单线程的。一次仅仅运行一个任务。完毕当前订单后再处理下一个。
- 你有多个厨师,每一个厨师都能够做,空暇的厨师准备着接单做饭:
- 这样的方式是多线程的,任务由多个线程(厨师)运行。能够并行同一时候运行。
- 你有多个厨师并分成组,一组做晚餐,一个做其它:
- 这样的情况也是多线程,可是带有额外的限制;同一时候运行多个任务是由实际运行的任务类型(晚餐或其它)决定。
从上面的样例看出。日常活动适合在一个线程模型。可是Netty在这里适用吗?不幸的是,它没有那么简单。Netty的核心是多线程,但隐藏了来自用户的大部分。
Netty使用多个线程来完毕全部的工作。仅仅有一个线程模型线型暴露给用户。大多数现代应用程序使用多个线程调度工作。让应用程序充分使用系统的资源来有效工作。在早期的Java中,这样做是通过按需创建新线程并行工作。
但非常快发现者不是完美的方案,由于创建和回收线程须要较大的开销。
在Java5中增加了线程池,创建线程和重用线程交给一个任务运行,这样使创建和回收线程的开销降到最低。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
使用多线程在刚開始可能没有什么问题。但随着系统的负载添加。可能在某个点就会让系统崩溃。
让我们来看看Netty是怎样解决问题的。
15.2 事件循环
15.2.1 使用事件循环
Channel ch = ...;
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
System.out.println("run in the eventloop");
}
});
Channel ch = ...;
Future<?> future = ch.eventLoop().submit(new Runnable() {
@Override
public void run() { }
});
if(future.isDone()){
System.out.println("task complete");
}else {
System.out.println("task not complete");
}
检查运行任务是否在事件循环中:
Channel ch = ...;
if(ch.eventLoop().inEventLoop()){
System.out.println("in the EventLoop");
}else {
System.out.println("outside the EventLoop");
}
仅仅有确认没有其它EventLoop使用线程池了才干关闭线程池,否则可能会产生没有定义的副作用。
15.2.2 Netty4中的I/O操作
这些读和写操作是网络API的一部分,通过java和底层操作系统提供。
下图显示在EventLoop上下文中运行入站和出站操作。假设运行线程绑定到EventLoop。操作会直接运行;假设不是,该线程将排队运行:
我们在了解Netty3后会更easy理解为什么新的线程模型是可取的。
15.2.3 Netty3中的I/O操作
问题是。其实,你如今的情况是在调用线程上运行,但捕获到异常事件必须交给工作线程来运行。这是可行的,但如果你忘了传递过去。它会导致线程模型失效;如果入站事件仅仅有一个线程不是真,这可能会给你各种各样的竞争条件。
- 字节写入到远程对等通道有多快
- I/O线程是否繁忙
- 上下文切换
- 锁定
你能够看到非常多细节影响总体延迟。
15.2.4 Netty线程模型内部
假设线程是同样的EventLoop中的一个。讨论的代码块被运行;假设线程不同,它安排一个任务并在一个内部队列后运行。
一般是通过EventLoop的Channel仅仅运行一次下一个事件,这同意直接从不论什么线程与通道交互,同一时候还确保全部的ChannelHandler是线程安全。不须要操心并发訪问问题。
这多少会影响整个系统依赖于EventLoop实现用于特殊传输的实现。
传输之间的切换在你的代码库中可能没有不论什么改变,重要的是:切勿堵塞I/O线程。假设你必须做堵塞调用(或运行须要长时间才干完毕的任务),使用EventExecutor。
15.3 调度任务运行
15.3.1 使用普通的Java API调度任务
- newScheduledThreadPool(int)
- newScheduledThreadPool(int, ThreadFactory)
- newSingleThreadScheduledExecutor()
- newSingleThreadScheduledExecutor(ThreadFactory)
看以下代码:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(10);
ScheduledFuture<?> future = executor.schedule(new Runnable() {
@Override
public void run() {
System.out.println("now it is 60 seconds later");
}
}, 60, TimeUnit.SECONDS);
if(future.isDone()){
System.out.println("scheduled completed");
}
//.....
executor.shutdown();
15.3.2 使用EventLoop调度任务
使用ScheduledExecutorService工作的非常好,可是有局限性,比方在一个额外的线程中运行任务。假设须要运行非常多任务。资源使用就会非常严重;对于像Netty这种高性能的网络框架来说,严重的资源使用是不能接受的。Netty对这个问题提供了非常好的方法。
Channel ch = ...;
ch.eventLoop().schedule(new Runnable() {
@Override
public void run() {
System.out.println("now it is 60 seconds later");
}
}, 60, TimeUnit.SECONDS);
Channel ch = ...;
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("after run 60 seconds,and run every 60 seconds");
}
}, 60, 60, TimeUnit.SECONDS);
// cancel the task
future.cancel(false);
15.3.3 调度的内部实现
Netty内部实现事实上是基于George Varghese提出的“Hashed and hierarchical timing wheels: Data structures to efficiently implement timer facility(散列和分层定时轮:数据结构有效实现定时器)”。这样的实现仅仅保证一个近似运行,也就是说任务的运行可能不是100%准确;在实践中。这已经被证明是一个可容忍的限制,不影响多数应用程序。所以。定时运行任务不可能100%准确的按时运行。
- 在指定的延迟时间后调度任务。
- 任务被插入到EventLoop的Schedule-Task-Queue(调度任务队列);
- 假设任务须要立即执行。EventLoop检查每一个执行;
- 假设有一个任务要运行,EventLoop将立马运行它,并从队列中删除。
- EventLoop等待下一次执行。从第4步開始一遍又一遍的反复。
由于这种实现计划运行不可能100%正确,对于多数用例不可能100%准备的运行计划任务;在Netty中,这种工作差点儿没有资源开销。可是假设须要更准确的运行呢?非常easy,你须要使用ScheduledExecutorService的还有一个实现,这不是Netty的内容。
记住。假设不遵循Netty的线程模型协议,你将须要自己同步并发訪问。
15.4 I/O线程分配细节
这三个线程会分配给每一个新创建的已连接通道,这是通过EventLoopGroup实现的。使用线程池来管理资源;实际会平均分配通道到全部的线程上,这样的分布以循环的方式完毕。因此它可能不会100%准确,但大部分时间是准确的。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">
我们能够使用java.io.*包里的类来开发基于堵塞I/O的应用程序,即使语义改变了。但有一件事仍然保持不变,每一个通道的I/O在同一时候仅仅能被一个线程处理;这个线程是由Channel的EventLoop提供,我们能够依靠这个硬性的规则,这也是Netty框架比其它网络框架更easy编写的原因。
15.5 Summary
Netty In Action中文版 - 第十五章:选择正确的线程模型的更多相关文章
- “全栈2019”Java多线程第三十五章:如何获取线程被等待的时间?
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- “全栈2019”Java多线程第十五章:当后台线程遇到finally
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Gradle 1.12 翻译——第十五章. 任务详述
有关其他已翻译的章节请关注Github上的项目:https://github.com/msdx/gradledoc/tree/1.12,或访问:http://gradledoc.qiniudn.com ...
- 15第十五章UDF用户自定义函数(转载)
15第十五章UDF用户自定义函数 待补上 原文链接 本文由豆约翰博客备份专家远程一键发布
- 《Linux命令行与shell脚本编程大全》 第十五章 学习笔记
第十五章:控制脚本 处理信号 重温Linux信号 信号 名称 描述 1 HUP 挂起 2 INT 中断 3 QUIT 结束运行 9 KILL 无条件终止 11 SEGV 段错误 15 TERM 尽可能 ...
- CSS3秘笈复习:十三章&十四章&十五章&十六章&十七章
第十三章 1.在使用浮动时,源代码的顺序非常重要.浮动元素的HTML必须处在要包围它的元素的HTML之前. 2.清楚浮动: (1).在外围div的底部添加一个清除元素:clear属性可以防止元素包围浮 ...
- Gradle 1.12用户指南翻译——第四十五章. 应用程序插件
本文由CSDN博客貌似掉线翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
- Gradle 1.12用户指南翻译——第二十五章. Scala 插件
其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Github上的地址: https://g ...
- Gradle 1.12用户指南翻译——第三十五章. Sonar 插件
本文由CSDN博客万一博主翻译,其他章节的翻译请参见: http://blog.csdn.net/column/details/gradle-translation.html 翻译项目请关注Githu ...
随机推荐
- PCB 一键远程桌面+RDP文件生成
最近在写个内网INCAM内网授权工具中,在服务端监听客户端请求后,后台自动处理客户端请求并远程客户端 这里记录3个点. 一.运行RDP文件后,正常会有下图2个弹窗,怎么可以关闭这2个弹窗呢, 通过模拟 ...
- PCB Genesis原点坐标转换关系
一.Genesis原点坐标转换关系: 1.读取Genesis坐标转换: UI界面坐标 = 文件坐标 - 偏移值 2.写入Genesis坐标转换: 文件坐标 = UI界面坐标 + 偏移值 3.为 ...
- [Apple开发者帐户帮助]二、管理你的团队(2)更改团队成员角色
如果您已加入Apple开发者计划,您将在App Store Connect中管理团队成员.有关详细信息,请转到App Store Connect帮助中的添加和编辑用户. 如果您已加入Apple Dev ...
- WebService的概念
一.序言 大家或多或少都听过 WebService(Web服务),有一段时间很多计算机期刊.书籍和网站都大肆的提及和宣传WebService技术,其中不乏很多吹嘘和做广告的成 分.但是不得不承认的是W ...
- ACM_一道耗时间的水题
一道耗时间的水题 Time Limit: 2000/1000ms (Java/Others) Problem Description: Do you know how to read the phon ...
- CSS浮动的处理
之前已经发过一遍有关浮动的解决办法,今天看到一些资料后又有了新的想法: 在CSS布局中float属性经常会被用到,但使用float属性后会使其在普通流中脱离父容器,让人很苦恼 1 浮动带来布局的便利, ...
- HTML学习笔记——DOCTYPE和DTD,标准模式和兼容模式
主要涉及知识点: HTML与XHTML HTML与XHTML的区别 DOCTYPE与DTD的概念 DTD的分类以及DOCTYPE的声明方式 标准模式(Standard Mode)和兼容模式(Quirc ...
- Java关于反射的用法
一. 首先是准备一个需要反射的类 public class Person { private String name; private int age; public String sex; publ ...
- ★Java语法(五)——————————三元运算符
package 课上练习; public class 三元运算符 { //用法: 数据类型 变量 = 布尔表达式? 条件满足设置内容:条件不满足设置内容 : public static void ma ...
- C#多线程方法 可传参
//将线程执行的方法和参数都封装到一个类里面.通过实例化该类,方法就可以调用属性来实现间接的类型安全地传递参数.using System; using System.Threading; //Thre ...