第8章-WorkerThread

8.1 模式简介

  1. 这是一个来自工作车间的故事。在这里,工人们负责组装塑料模型。
  2. 客户会将很多装有塑料模型的箱子带到工作车间来,然后摆放在桌子上。
  3. 工人必须将客户送过来的塑料模型一个一个组装起来。他们会先取回放在桌子上的装有塑料模型的箱子,然后在阅读了箱子中的说明书后开始组装。当一箱模型组装完成后,工人们会继续去取下一个箱子。当所有模型全部组装完成后,工人们会等待新的模型被送过来。
  4. Worker的意思是工作的人、劳动者。在Worker Thread模式中,工人线程(worker thread)会逐个取回工作并进行处理。当所有工作全部完成后,工人线程会等待新的工作到来。
  5. Worker Thread 模式也被称为 Background Thread(背景线程)模式。另外,如果从 “保存多个工人线程的场所” 这一点来看,我们也可以称这种模式为 Thread Pool(线程池)模式。

8.3 Worker Thread模式中的角色

8.3.1 Client(委托者)

  1. Client角色创建表示工作请求的Request角色并将其传递给Channel角色。

8.3.2 Channel(通信线路)

  1. Channel角色接收来自于Client角色的Request角色,并将其传递给Worker角色。在示例程序中,由Channel类扮演此角色。

8.3.3 Worker(工人)

  1. Worker角色从Channel角色中获取Request角色,并进行工作。当一项工作完成后,它会继续去获取另外的Request角色。

8.3.4 Request(请求)

  1. Request角色是表示工作的角色。Request角色中保存了进行工作所必需的信息。
    Worker Thread模式的类图如图8-4所示,Timethreads图如图8-5所示。

8.3.5 类图和Timethreads图

类图

Timethreads图

8.4 拓展思路的要点

8.4.1 提高吞吐量

  1. 如果可以将自己的工作交给其他人,那么自己就可以做下一项工作。线程也是一样的。如果将工作交给其他线程,自己就可以做下一项工作。这是Thread-Per-Message模式的主题。
  2. 由于启动新线程需要花费时间,所以Worker Thread模式的主题之一就是通过轮流地和反复地使用线程来提高吞吐量。
  3. Worker Thread模式是否实际地提高了吞吐量取决于线程的启动时间

8.4.2 容量控制

  1. Worker Thread模式还有另外一个主题,那就是可以同时提供的服务的数量,即容量的控制。
8.4.2.1 Worker角色的数量
  1. Worker角色的数量越多,可以并发进行的处理也越多。但是,即使Worker角色的数量超过了同时被请求的工作的数量,也不会对提高程序处理效率有什么帮助。因为多余的Worker角色不但不会工作,还会占用内存。增加容量就会增加消耗的资源,所以必须根据程序实际运行的环境来相应地调整Worker角色的数量。
  2. Worker角色的数量不一定必须在程序启动时确定,也可以像下面这样动态地改变Worker角色的数量:
    • 最开始只有几个Worker角色
    • 当工作增加时就增加Worker角色
    • 但是,如果增加得太多会导致内存耗尽,因此到达极限值后就不再增加Worker角色
    • 反之,当工作减少(即等待工作的Worker角色增加)时,就要逐渐减少Worker角色
8.4.2.2 Request角色的数量
  1. Channel角色中保存着Request角色。只要Worker角色不断地进行工作,在Channel角色中保存的Request角色就不会增加很多。不过,当接收到的工作的数量超出了Worker角色的处理能力后,Channel角色中就会积累很多Request角色。这时,Client角色必须等待一段时间才能将Request角色发送给Channel角色。
  2. 如果Channel角色可以保存很多Request角色,那么就可以填补(缓冲)Client角色与Worker角色之间的处理速度差异。但是,保存Request角色会消耗大量的内存。因此,这里我们需要权衡容量与资源

8.4.3 调用与执行的分离

  1. Client角色负责发送工作请求。它会将工作内容封装为Request角色,然后传递给Channel角色。在普通的方法调用中,这部分相当于 “设置参数并调用方法” 。其中,“设置参数” 与 “创建Request角色” 相对应,而 “传递给Channel角色” 大致与 “调用方法” 相对应。

  2. Worker角色负责进行工作。它使用从Channel角色接收到的Request角色来执行实际的处理。在普通的方法调用中,这部分相当于 “执行方法”。

  3. 在进行普通的方法调用时,“调用方法” 和 “执行方法”是连续进行的。因为调用方法后,方法会立即执行。在普通的方法调用中,调用与执行是无法分开的。

  4. 但是,在Worker Thread模式和Thread-Per-Message模式中,方法的调用和方法的执行是特意被分开的。方法的调用被称为 invocation(动词为invoke),方法的执行则被称为 execution(动词为execute)。因此,可以说Worker Thread 模式和Thread-Per-Message模式将方法的调用(invocation)和执行(execution)分离开来了。调用与执行的分离同时也是Command模式的主题之一。

  5. 调用和执行分离究竟有什么意义

    • 提高响应速度:如果调用和执行不可分离,那么当执行需要花费很长时间时,就会拖调用处理的后腿。但是如果将调用和执行分离,那么即使执行需要花费很长时间也没有什么关系,因为执行完调用处理的一方可以先继续执行其他处理,这样就可以提高响应速度。
    • 控制执行顺序(调度):如果调用和执行不可分离,那么在调用后就必须开始执行。但是如果将调用和执行分离,执行就可以不再受调用顺序的制约。我们可以通过设置Request角色的优先级,并控制Channel角色将Request角色传递给Worker角色的顺序来实现上述处理。这种处理称为请求调度(scheduling)
    • 可以取消和反复执行:将调用和执行分离后,还可以实现 “即使调用了也可以取消执行” 这种功能。由于调用的结果是Request角色对象,所以既可以将Request角色保存,又可以反复地执行。
    • 通往分布式之路:将调用和执行分离后,可以将负责调用的计算机与负责执行的计算机分离开来,然后通过网络将扮演Request角色的对象从一台计算机传递至另外一台计算机。

8.4.4 Runnable接口的意义

  1. java.lang.Runnable 接口有时会被用作Worker Thread模式中的Request角色。也就是说,该模式会创建一个实现了Runnable接口的类的实例对象(Runnable对象)来表示工作内容,然后将它传递给Channel角色,让其完成这项工作。
  2. 但是,Runnable接口的使用方法并非仅仅如此:Runnable对象可以作为方法参数传递,可以被放入到队列中,可以跨越网络传递,也可以被保存至文件中。然后,这样的Runnable对象不论被传递到哪台计算机中的哪个线程中,都可以运行。

8.4.5 多态的Request角色

  1. ClientThread传递给Channel的只是Request的实例。但是,WorkerThread并不知道Request类的详细信息。WorkerThread只是单纯地接收Request的实例,然后调用它的execute方法而已。
  2. 也就是说,即使我们编写了一个Request类的子类并将它的实例传递给了Channel,WorkerThread也可以正常地调用execute方法。用面向对象的术语来说,就是这里使用了多态性(polymorphism)。
  3. Request角色中包含了完成工作所必需的全部信息。因此,即使我们实现了多态的Request角色并增加了工作的种类,也无需修改Channel角色和Worker角色。这是因为即使工作种类增加了,Worker角色依然只是调用execute方法而已

多态Request类图

8.4.6 独自一人的Worker角色

  1. 当工人线程只有一个时,由于工人线程进行处理的范围变成了单线程,所以会有互斥处理可以省略的可能性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值