第7章-ThreadPerMessage

7.1 模式简介

  1. 上司把文件递给下属:“能帮我传真一下这个文件吗?” 妻子告诉丈夫:“老公,帮忙倒一下垃圾”。像这样将工作委托给其他人的情况很常见。这个人把工作拜托给别人之后,就可以返回继续做自己的工作。
  2. 所谓Per,就是 “每~” 的意思。因此,Thread Per Message直译过来就是 “每个消息一个线程” 的意思。Message在这里可以理解为 “命令” 或 “请求” 。为每个命令或请求新分配一个线程,由这个线程来执行处理——这就是Thread-Per-Message模式。
  3. 在Thread-Per-Message模式中,消息的 “委托端” 和 “执行端” 是不同的线程。消息的委托端线程会告诉执行端线程 “这项工作就交给你了”。

7.3 Thread-Per-Message模式中的角色

7.3.1 Client(委托人)

  1. Client角色会向Host角色发出请求(request),但是并不知道Host角色是如何实现该请求的。

7.3.2 Host

  1. Host角色收到Client角色的请求(request)之后,会新创建并启动一个线程。新创建的线程将使用Helper角色来 “处理”(handle)请求。

7.3.3 Helper(助手)

  1. Helper角色为Host角色提供请求处理的功能。Host角色创建的新线程会利用Helper角色。

7.3.4 类图和Timethreads图

类图

Timethreads图

7.4 拓展思路的要点

7.4.1 提高响应性,缩短延迟时间

  1. Thread-Per-Message模式能够提高与Client角色对应的Host角色的响应性,降低延迟时间。尤其是当handle操作非常耗时,或者handle操作需要等待输入/输出时,效果非常明显。
  2. 在Thread-Per-Message模式下,Host角色会启动新的线程。由于启动线程也会花费时间,所以想要提高响应性时,是否使用Thread-Per-Message模式取决于 “handle操作花费的时间” 和 “线程启动花费的时间” 之间的均衡。

7.4.2 适用于操作顺序没有要求时

  1. 在Thread-Per-Message模式中,handle方法并不一定是按request方法的调用顺序来执行的。因此,当操作要按某种顺序执行时,Thread-Per-Message模式并不适用。

7.4.3 适用于不需要返回值时

  1. 在Thread-Per-Message模式中,request方法并不会等待handle方法执行结束。所以request得不到handle的运行结果。因此,Thread-Per-Message模式适用于不需要获取返回值的情况。例如通知某个事件时。

7.4.4 应用于服务器

  1. 为了使服务器可以处理多个请求,我们可以使用Thread-Per-Message模式。服务器本身的线程接收客户端的请求,而这些请求的实际处理则交由其他线程来执行,服务器本身的线程则返回,去等待客户端的其他请求

7.6 进程与线程

7.6.1 线程之间共享内存

  1. 进程与线程之间最大的区别就是内存是否共享
  2. 通常,每个进程都拥有彼此独立的内存空间。一个进程不可以擅自读取、写入其他进程的内存。由于进程的内存空间是彼此独立的,所以一个进程无须担心被其他进程破坏。
  3. 线程之间共享内存,我们经常让一个线程向内存中写入内容,来供其他线程读取。所谓 “共享内存”,在 Java中就是 “共享实例” 的意思。Java的实例分配在内存上,可由多个线程进行读写。
  4. 由于线程之间共享内存,所以线程之间的通信可以很自然、简单地实现。一个线程向实例中写入内容,其他线程就可以读取该实例的内容。而由于多个线程可以访问同一个实例,所以我们必须正确执行互斥处理。

7.6.2 线程的上下文切换快

  1. 进程和线程之间的另一个区别就是上下文切换的繁重程度
  2. 当运行中的进程进行切换时,进程要暂时保存自身的当前状态(上下文信息)。而接着开始运行的进程需要恢复之前保存的自身的上下文信息。这种信息切换(context-switch)比较花费时间。
  3. 当运行中的线程进行切换时,与进程一样,也会进行上下文切换。但由于线程管理的上下文信息比进程少,所以一般来说,线程的上下文切换要比进程快。因此,当执行紧密关联的多项工作时,通常线程比进程更加适合。

7.7 java.util.concurrent 包和Thread-Per-Message模式

  • Thread-Per-Message模式会为每个请求创建并启动线程。下面将介绍 “并启动线程” 的各种实现形式。

7.7.1 java.lang.Thread类

  1. 使用new来创建Thread类的实例,并调用 start 方法来启动线程。这是最最基本的内容。

7.7.2 java.lang.Runnable接口

  1. 使用new来创建Thread实例时,构造函数中将传入Runnable接口的实现类的实例。

7.7.3 java.util.concurrent.ThreadFactory接口

  1. java.util.concurrent.ThreadFactory 接口声明了如下所示的一个newThread方法:
    java Thread newThread(Runnable r)
  2. ThreadFactory是将线程创建抽象化了的接口,参数中的Runnable对象表示线程执行的操作内容。
  3. 使用new创建Thread实例时,代码依赖于java.lang.Thread类。这时,我们无法控制创建线程的部分,可复用性较低。假如我们用字段threadFactory来保存ThreadFactory对象,用threadFactory.newThread(…)来替代new Thread(……)。这样一来,只要替换赋给threadFactory的ThreadFactory对象,我们便可以控制线程创建了。

7.7.4 java.util.concurrent.Executors 类获取的ThreadFactory

  1. java.util.concurrent.Executors 类提供了许多实用的静态方法。例如,Executors.defaultThreadFactory()表达式可以获取当前默认设置的ThreadFactory对象。

7.7.5 java.util.concurrent.Executor接口

  1. java.util.concurrent.Executor接口声明了如下所示的一个execute方法:
    java void execute(Runnable r)
  2. Executor接口将某些“处理的执行”抽象化了,参数传入的Runnable对象表示 “执行的处理” 的内容。
  3. 前面介绍的ThreadFactory接口隐藏了线程创建的细节,但并未隐藏创建线程的操作。如果使用Executor接口,创建线程的操作也可以隐藏起来

7.7.6 java.util.concurrent.ExecutorService接口

  1. 在前面的程序中,虽然我们使用Executor进行了抽象化,但最终还是需要自己手动执行new Thread(…)。不过仔细想想,并不一定每次都必须创建线程。只要遵循Executor接口,我们也可以使用某些 “会复用那些处理执行结束后空闲下来的线程” 的类。
  2. 这就是ava.util.concurrent.Executorservice 接口。Executorservice接口对可以反复 execute 的服务进行了抽象化。线程一直在后台运行着,每当调用execute方法时,线程就会执行Runnable对象。
  3. 通常情况下,在ExecutorService接口后面,线程是一直运行着的,所以Executorservice接口提供了shutdown方法来结束服务。
  4. 由于ExecutorService接口是Executor的子接口,所以接收Executor对象的Host类也可以接收Executorservice对象。

7.7.7 java.util.concurrent.ScheduledExecutorService类

  1. java.util.concurrent.ScheduledExecutorService 接口是ExecutorService的子接口,用于推迟操作的执行
  2. schedule方法位于ScheduledExecutorService接口中,可以用于设置Runnable对象(r)和延迟时间(delay、unit)。
    java schedule(Runnable r,long delay,TimeUnit unit)
  3. long 类型的delay表示的是延迟时间,TimeUnit类型的unit表示的则是指定延迟时间的单位(NANOSECONDS、MICROSECONDS、MILLISECONDS或SECONDS)。

7.7.8 总结

  1. java.lang.Thread类:最基本的创建、启动线程的类
  2. java.lang.Runnable接口:表示线程所执行的 “工作” 的接口
  3. java.util.concurrent.ThreadFactory接口:将线程创建抽象化了的接口
  4. java.util.concurrent.Executor接口:将线程执行抽象化了的接口
  5. java.util.concurrent.ExecutorService接口:将被复用的线程抽象化了的接口
  6. java.util.concurrent.ScheduledExecutorService接口:将被调度的线程的执行抽象化了的接口
  7. java.util.concurrent.Executors类:用于创建实例的工具类

类图