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 ...
随机推荐
- 强迫症!一行代码拿到url特定query的值
简单的说一下背景,看到小伙伴给我发的项目中有一段获取当前url特定query值的代码,本着能写1行代码就不写5行代码的原则,我把这个获取方法给改了一下 之前的代码如下: const queryArr ...
- 深入理解Redis(番外)——持久化
引语 Redis作为一款内存数据库,自然所有数据都加载在内存中,那么自然就有小伙伴会问,如果服务器宕机了怎么办,数据不都丢了吗,不用担心,Redis早就提供了两种方式来将数据进行持久化,即便服务器宕机 ...
- POJ 2976 裸的01分数规划
题意:给你n个数对(认为是a数组和b数组吧),从中取n-m个数对,如果选第i个数对,定义x[i]=1,求R=∑(a[i]*x[i])/∑(b[i]*x[i])取得最大值时R的值.输出R*100(保留到 ...
- Asp.net MVC4 Step by Step (3)-数据验证
ASP.NET MVC把数据验证集成到了请求处理过程中,控制器操作可以通过查询ModelState 来检查请求是否有效, 下面判断了ModelState的有效性后进行“保存或返回”操作. [Htt ...
- OI知识点
- 安装完MongoDB后尝试mongod -dbpath命令为什么会一直卡在连接端口?
1.现象如下 Linux Windows 2.原因 其实,这不是卡住了,而是告诉我们.数据库已经启动,而且这个东东还不能关掉,关掉意味着数据库也关了.一开始我也是傻逼逼的在那等了一天,哎.... 3. ...
- Android测试写入文本Log
写入本地SD卡: @SuppressLint("SdCardPath") public void writeFileSdcard(String fileName, String m ...
- 解决Cannot change version of project facet Dynamic Web M 3.0
解决Cannot change version of project facet Dynamic Web M 3.0 dynamic web module 版本之间的区别: Servlet 3.0 D ...
- PAT_A1153#Decode Registration Card of PAT
Source: PAT A1153 Decode Registration Card of PAT (25 分) Description: A registration card number of ...
- 原来这才是Kafka的“真面目”
作者介绍 郑杰文,腾讯云存储,高级后台工程师,2014 年毕业加入腾讯,先后从事增值业务开发.腾讯云存储开发.对业务性.技术平台型后台架构设计都有深入的探索实践.对架构的海量并发.高可用.可扩展性都有 ...