Open funnycoding opened 4 years ago
应用程序通常会创建拥有多个线程的服务,例如线程池。
并且这些服务的生命周期通常比创建它们的方法的生命周期更长。 如果应用程序准备退出,那么这些服务所拥有的线程也需要结束。
应用程序通常会创建拥有多个线程的服务,例如线程池。
并且这些服务的生命周期通常比创建它们的方法的生命周期更长。 如果应用程序准备退出,那么这些服务所拥有的线程也需要结束。
由于无法通过抢占式的方法来停止线程,因此它们需要「自行结束」。 **正确的封装原则**是:**除非拥有某个线程,否则不能对线程进行操控。** 例如:中断线程或者修改线程的优先级等。 在线程 API 中,并没有对线程的所有权给出正式的定义:**线程由 Thread 对象表示**,并且像其他对象一样可以被自由共享。 然而线程有一个**相应的所有者**,即**创建该线程的类**。 因此**线程池是其工作者线程的所有者**<---工作线程在线程池内被创建,**如果要中断这些线程,那么应该使用线程池**。【<---通过线程池来做中断,而不是直接通过工作线程本身来中断?】 与其他封装对象一样,**线程的所有权是不可传递的**:应用程序可以拥有服务,服务也可以拥有工作线程,但应用程序并不能拥有工作者线程,因此**应用程序不能直接停止工作者线程**。 相反,服务应该提供**生命周期方法(Lifecycle Method)**来关闭它自己以及它所拥有的线程。 这样,当应用程序关闭该服务时,服务就可以关闭所有的线程了。 在 `ExecutorService` 中提供了 `shutdon` 和 `shutdownNot` 等方法。 同样,在其他拥有线程的服务中,也应该提供类似的关闭机制。 > 对于持有线程的服务,只要服务的存在时间大于创建线程的方法的存在时间,那么就应该提供生命周期方法。 #### 7.2.1 示例:日志服务 在大多数服务器应用程序中都会用到日志,例如在代码中插入 `println` 语句就是一种简单的日志。 像 `PrintWriter` 这样的**字符流类是线程安全**的,因此这种简单的方法不需要显示的同步。 但是如果需要在**单条日志信息中写入多行**,那么要通过**「客户端加锁」**来避免多个不正确地交错输出。如果两个线程同时把多行栈追中信息**(Stack Trace)** 添加到同一个流中,并且每行信息对应一个 `println` 调用,那么这些**信息在输出中将交错在一起**,看上去就是一些虽然庞大但毫无意义的栈追踪信息。 然而,在11.6节中将看到这种**内联日志功能会给一些高容量的(Highvolume)应用程序带来一定的性能开销。** 另一种替代方法是通过调用 `log` 方法**将日志消息放入某个「队列」中,并由「其他线程」来处理。** 在**程序清单 7-13** 的 `LogWriter` 中给出了一个简单的日志服务示例,其中日志操作是在单独的**「日志线程」** 中执行的。产生日志信息的线程并不会将消息直接写入输出流,而是由 `LogWriter` 通过 `BlockingQueue` 将信息提交给日志线程,并由日志线程写入。 这是一种 **多生产者单消费者(Multiple-Producer,Single-Consumer)** 的设计方式:每个调用 `log` 的操作都相当于一个生产者,而后台的日志线程则相当于消费者。 如果消费者的处理速度低于生产者的生成速度,那么 `BlockingQueue` 将阻塞生产者,直到日志线程有能力处理新的日志消息。 > **程序清单 7-13** 不支持关闭的 生产者—消费者 日志服务: ```java // 不支持关闭的 生产者-消费者服务,没有终止线程的方法 public class LogWriter { private final BlockingQueue