Netty4实战第十五章:选择正确的线程模型
来源:互联网 发布:淘宝refa旗舰店真的吗 编辑:程序博客网 时间:2024/05/18 02:46
本章主要内容:
- 线程模型的知识
- EventLoop
- 并发
- 任务执行器
- 任务定时执行
- 理解什么是线程并且有一定的使用经验。如果还没使用过线程的,最好去了解一下线程的基础,再来学习本章
- 理解多线程应用,包括线程安全方面的知识
- 最好理解并使用过java.util.concurrent包里面的ExecutorService和ScheduledExecutorService
一、线程模型
在这一小节,我们会学习什么是线程模型,Netty4使用的是什么线程模型,Netty4之前的版本使用的是什么样的线程模型。会更好地理解不同线程模型之间的优缺点。
其实仔细想一想,大家在实际生活中其实都使用了线程模型。为了更好的理解什么是线程模型,我们使用一个现实生活中的例子进行类比。
比如大家有一个饭店,厨房要做好饭菜然后送到顾客桌子上。一个顾客进来点了菜,你就需要告诉厨房要做什么菜。你的厨房可以使用不同的方式处理这些订单-每一种方式类似一个线程模型如何执行任务。
- 一个厨师一次只做一道菜,这类似单线程模型,也就是一个时间点只有一个任务在执行。这个任务完成了,再去执行下一个任务
- 多个厨师,哪个厨师没事做就去做客人点的菜。这类似与多线程模型,多个线程执行多个任务,也就是说那些任务可以同时执行
- 多个厨师,但是进行了分组,这组炒菜,这组煮面,那组蒸米饭。这也是多线程模型,不过有额外的限制。虽然多个任务也是同时执行,但是不同类型的任务分在了不同的组执行,如炒菜,面条,米饭
在学习更底层的知识之前,我们先通过其他大部分应用的做法来理解线程模型。
大多数现代应用程序都会使用多个线程来分发工作,因此可以有效的利用系统的资源。在早起的Java版本中,如果需要并行的处理任务,是通过创建线程来做到的。
但很快大家发现这种方式并不完美,因为创建线程然后回收资源是有不小的系统开销的。然后到了Java 5有了线程池技术,使用了同一个接口Executor。Java 5提供了很多线程池的实现,虽然它们的内部结构都不一样,但是它们的思想是一样的。当需要多线程执行任务时,线程池创建线程并尽可能重用线程。这样就可以尽可能降低创建与销毁线程的系统开销。
下图展示了线程池如何使用多线程执行任务的。任务提交之后由空闲线程去执行,任务执行完成之后,就会释放掉线程。
线程池的技术,解决了线程创建与销毁的系统开销,因为对于每一个新任务,它不需要去创建线程,只需要使用线程池中的空闲线程。但这也只解决了问题的一半,后面再详细讲解。
为什么不在所有情况下都使用多线程呢?毕竟现在有了ExecutorService这种线程池技术帮我们降低了创建回收线程的系统资源开销。
使用多线程除了创建回收线程的系统开销,还会带来其他的副作用,如资源管理,上下文切换等。随着线程数量和任务数量的增加,这些副作用就会越大。刚使用多线程的时候可能不会出现什么问题,但是到真正在大型系统中使用多线程就可能出现很大的问题。
除了上面说的这些技术限制和问题外,使用多线程技术另一个大问题就是项目或框架的维护成本。可以说应用的并发特性会导致项目复杂很多。总结起来就是很简单的一句话:编写多线程应用是一个很难的工作。我们能做什么来解决这些问题呢?因为实际项目中很多地方都需要多线程场景。我们来看看Netty是如何解决这些问题的。
二、EventLoop
EventLoop这个名字取的很形象。意思就是事件执行在一个循环中直到被终止。这种设计很适合网络框架,因为它需要在一个循环中为指定的连接执行事件逻辑。这并不是Netty新发明的,其他项目和框架很早以前就使用了这种设计。
Netty的事件循环代表是EventLoop接口。EventLoop继承了EventExecutor,EventExecutor继承了ScheduledExecutorService,也就是任务可以直接交给EventLoop来执行。EventExecutor的继承关系图如下。
因为EventLoop会指定给Channel,所以Channel的事件就可以交给EventLoop执行,类似通常使用ExecutorService执行多线程任务。
2.1、使用EventLoop
下面的代码展示了如何使用指定给Channel的EventLoop执行任务。
Channel ch = ... Future<?> future = ch.eventLoop().execute(new Runnable() { @Override public void run() { System.out.println("Run in the EventLoop"); } });直接使用EventLoop的execute方法就可以执行线程任务了,并且不需要担心一些同步问题。因为一个Channel相关的任务都会在同一个线程里执行。这就是Netty需要的线程模型。
可以利用返回的Future来检查任务是否执行完成。
Channel channel = ... Future<?>future = channel.eventLoop().submit(…); if (future.isDone()) { System.out.println("Task complete"); } else { System.out.println("Task not complete yet"); }还可以通过检查任务是否在EventLoop中来确定任务是否将被直接执行,如下。
Channel ch = ... if (ch.eventLoop().inEventLoop()) { System.out.println("In the EventLoop"); } else { System.out.println("Outside the EventLoop"); }只有在确定没有其他EventLoop使用线程池时再去关闭线程池,否则则会出现一些不明确的影响。
2.2、Netty4的IO操作
上一小节实现的线程模型非常强大,Netty就是使用它处理IO事件的,也就是触发Socket上的读写操作。这些读写操作是Java和底层操作系统提供的网络API的一部分。
下图展示了Netty的进出数据操作是如何在EventLoop上下文中执行的。如果执行线程已经绑定到EventLoop则操作就会直接执行;如果没有则会放到队列里,待EventLoop准备好后再执行。
具体处理什么事件取决于事件的性质。通常是将网络栈中的数据读或传输到应用中,其他时候则是方向相反的同样操作,例如将应用中的数据传输到网络栈中用来发送给对端。但并不只限用于这种类型,重要的是它的逻辑是比较通用和灵活的,可以用来处理各种各样的情况。
另外就是Netty并不是一直使用上面说的EventLoop线程模型。下一小节就会介绍Netty3使用的线程模型。这样也能帮助我们更容易理解Netty4的线程模型为什么更好。
2.3、Netty3的IO操作
Netty3的线程模型和Netty4的有些不同。Netty3只保证读操作事件在IO线程中执行。写操作事件是通过调用线程来处理的。听起来这也是个好主意但结果证实这很容易出错。因为它需要同步ChannelHandler来处理事件,因为它不保证同一时间只有一个线程去操作。一个Channel在同一时间写多次数据就会出现这种问题,例如,在同时在不同线程调用Channel.write(..)方法。
除了需要同步ChannelHandler的副作用,Netty3的线程模型另外一个副作用就是处理发送数据事件时会触发收到消息事件。这是很有可能的,例如,当你使用Channel.write(..)方法时出现了异常。这个时候,exceptionCaught事件就会产生并被触发。咋一看这貌似也不是个问题,不过exceptionCaught设计的是一个收到消息事件,这样就会出现问题。实际上问题就是你的代码在调用的业务线程里面执行,但是exceptioncaught事件要交给工作线程去处理。通常如果能正确处理也没什么问题,但是如果忘记交给工作线程,就会导致线程模型失效,可能会带来很多竞争条件,使用一个线程去处理收到消息事件就不再可行了。
当然Netty3的线程模型也就是优点的,在某些情况它有更低的延迟,毕竟读写操作是分在了不同线程,但是它带来的复杂性远远大于它的优点。事实上,大多数应用程序也不能明显看出延迟的差异,因为这还依赖一些其他因素:
- 网络速度
- 实际的I/O线程是否繁忙
- 上下文切换
- 锁
2.4、Netty线程模型详情
三、延迟执行的调度任务
3.1、Java API执行调度任务
方法
描述
newScheduledThreadPool(int corePoolSize)
newScheduledThreadPool(int corePoolSize,ThreadFactorythreadFactory)
创建指定线程数量的调度任务执行器
newSingleThreadScheduledExecutor()
newSingleThreadScheduledExecutor(ThreadFactorythreadFactory)
创建单个线程的调度任务执行器
可能你觉得上面的方法提供的不是很多,但是大部分情况下都是够用的了。现在我们来看看如何使用ScheduledExecutorService执行一个60秒后的任务。ScheduledExecutorService executor = Executors.newScheduledThreadPool(10); ScheduledFuture<?> future = executor.schedule((Runnable) () -> { System.out.println("Now it is 60 seconds later"); }, 60, TimeUnit.SECONDS); executor.shutdown();可以看到使用ScheduledExecutorService是很容易发起一个调度任务的。
3.2、使用EventLoop发起调度任务
Channel ch = ... ScheduledFuture<?> future = ch.eventLoop().schedule(() -> System.out.println("Now its 60 seconds later"), 60, TimeUnit.SECONDS);如上面的代码所示,60秒后将由EventLoop去执行。
Channel ch = ... ScheduledFuture<?> future = ch.eventLoop() .scheduleAtFixedRate((Runnable) () -> System.out.println("Run every 60 seconds"), 60, 60, TimeUnit.SECONDS);如果想取消任务,可以利用返回的ScheduledFuture。ScheduledFuture提供了一些方法用于取消调度任务或者检查调度任务的状态。
ScheduledFuture<?> future = ch.eventLoop().scheduleAtFixedRate(..); future.cancel(false);更多ScheduledExecutorService的相关API可以去查看JDK文档。
3.3、调度任务的内部实现
- 创建一个调度任务
- 将调度任务插入EventLoop的任务执行队列
- 任务需要执行时由EventLoop去检查
- 检查通过则任务会立即执行并将任务从队列中移除
- 检查不通过等待下次再执行
四、I/O线程分配详情
五、总结
- Netty4实战第十五章:选择正确的线程模型
- Netty In Action中文版 - 第十五章:选择正确的线程模型
- Netty In Action中文版 - 第十五章:选择正确的线程模型
- 15 选择正确的线程模型
- netty4.0中EventExecutorGroup池中选择线程的方式
- sklearn:选择正确的模型
- Netty3 VS Netty4 之线程模型
- Netty3 VS Netty4 之线程模型
- Netty3 VS Netty4 之线程模型
- Netty3 VS Netty4 之线程模型
- Netty4实战第四章:Transports
- Netty4实战第五章:Buffers
- Netty4实战第六章:ChannelHandler
- Netty4实战第七章:编解码器
- Netty4实战第十一章:WebSockets
- Netty4实战第十二章:SPDY
- Netty4实战第八章:Netty提供的ChannelHandler和编解码器
- Netty4实战第十章:Netty应用的单元测试
- 苏宁 IT 总部消费者研发中心许宏平:如何用“黑科技”做“懂你”的门店?
- 传统物流商业模式难以为继,物联网等新技术带来了哪些新思路?
- 定了!苹果9月12日召开新品发布会
- Uber Movement正式上线,有望帮助解决城市交通拥堵问题
- 万券领先抢,9.9国美福利日最高优惠1180元
- Netty4实战第十五章:选择正确的线程模型
- 平安科技亮相重庆,「AI人脸识别+健康医疗」首秀
- logistics回归分类图片
- 使用robot framework 结合selenium 利用AutoIt工具识别上传 脚本在SciTE Script Editor运行正常,在RIDE中调用失败?
- Facebook利用AI算法“纠偏”360度照片
- 德国市占率第一的科沃斯携最新扫地机器人亮相IFA展
- 三星获得加州自动驾驶测试许可,与Google苹果正面刚
- sql 高级查询探索
- 自动驾驶离我们还有多远?