停止基于线程的服务
正确的封装原则是:除非拥有某个线程,否则不能对该线程进行操控。例如中断线程或修改线程的优先级等 。那么什么 是拥有某个线程呢,就是创建该线程的类,一般来说线程池是其工作线程的所有者,所以要修改线程的话,需要使用线程池来执行
线程的所有权是不能传递的。在ExecutorService中提供了shutdown和shutdownnow等 方法,同样,在其他拥有线程的服务中也应该提供类似的关闭机制。
对于持有线程 的服务,只要服务的存在时间 大于创建线程的方法 的存在时间,那么就应该提供生命周期方法
栗子:日志服务
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;public class LogWriter {private final BlockingQueue<String> queue;private final LoggerThread logger;private final int CAPACITY = 100;public LogWriter(Writer writer) {this.queue = new LinkedBlockingDeque<String>(CAPACITY);this.logger = new LoggerThread(writer);}public void start() {logger.start();}public void log(String msg) throws InterruptedException {queue.put(msg);}private class LoggerThread extends Thread {private final PrintWriter writer;public LoggerThread(Writer writer) {super();this.writer = (PrintWriter) writer;}@Overridepublic void run() {// TODO Auto-generated method stubwhile (true) {try {writer.println(queue.take());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {writer.close();}}}}public static void main(String[] args) {Writer writer = null;try {writer = new PrintWriter(new File("C://log.txt"));} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}LogWriter log = new LogWriter(writer);log.start();try {log.log("sdfds");log.log("sdfds");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
这是一个典型的多生产者一个消费者的设计方式,如果 消费者的速度小于生产者的速度,那么BlockingQueue会阻塞生产者,直到日志线程有能力处理新的日志消息
但是它不支持关闭,我们下面想办法把它关闭。第一种方法是将日志线程修改为当捕获到InterrupterException就退出 。也就是只关闭了消费者
这样有几个缺点,第一个就是会丢失将要处理的日志信息。第二,当其他线程调用log时被阻塞,因为日志消息队列是满的。因为这些线程将无法解除阻塞状态。但在这个示例中,生产者并不是一个专门的线程。因此要取消他们非常困难
第二种关闭logwriter的方法是:设置某个“已请求关闭”的标志,一收到关闭的时候就停止接收日志消息
private boolean shutdownRequested = false;public void log(String msg) throws InterruptedException {if (!shutdownRequested) {queue.put(msg);} elsethrow new IllegalStateException("logger is shut down ");}public void shutdownlog() {shutdownRequested = true;}
这同样存在着阻塞问题,log是一种先判断再运行的程序,当他判断的时候是关闭之前,然后开始阻塞put。在这个过程中如果服务被关闭了,那么同样也是会发生问题的。
下面是一个终极解决办法
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.Writer;
import java.rmi.server.LogStream;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;public class LogWriter {private final BlockingQueue<String> queue;private final LoggerThread logger;private final int CAPACITY = 100;private boolean isShutdown = false;private int reservations;public LogWriter(Writer writer) {this.queue = new LinkedBlockingDeque<String>(CAPACITY);this.logger = new LoggerThread(writer);}public void start() {logger.start();}public void stop() {synchronized (this) {isShutdown = true;}logger.interrupt();}public void log(String msg) throws InterruptedException {synchronized (this) {if (isShutdown) {throw new IllegalStateException("logger is shut down ");}++reservations;}queue.put(msg);}private class LoggerThread extends Thread {private final PrintWriter writer;public LoggerThread(Writer writer) {super();this.writer = (PrintWriter) writer;}@Overridepublic void run() {// TODO Auto-generated method stubwhile (true) {try {synchronized (this) {if (isShutdown && reservations == 0) {break;}}String msg = queue.take();synchronized (LogWriter.this) {--reservations;}writer.println(msg);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {writer.close();}}}}public static void main(String[] args) {Writer writer = null;try {writer = new PrintWriter(new File("C://log.txt"));} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();}final LogWriter log = new LogWriter(writer);log.start();try {log.log("sdfds");log.log("sdfds");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}
这次把logwriter改写成为原子性的操作。消除了在判断后阻塞的问题。并且引入了一个计数器。确保它可以把已经put的日志消费完,同时不再接收任何日志。这样当消费完毕的时候才会关闭日志服务。