停止基于线程的服务
正确的封装原则是:除非拥有某个线程,否则不能对该线程进行操控。例如中断线程或修改线程的优先级等 。那么什么 是拥有某个线程呢,就是创建该线程的类,一般来说线程池是其工作线程的所有者,所以要修改线程的话,需要使用线程池来执行
线程的所有权是不能传递的。在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;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
try {
writer.println(queue.take());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.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 block
e.printStackTrace();
}
LogWriter log = new LogWriter(writer);
log.start();
try {
log.log("sdfds");
log.log("sdfds");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这是一个典型的多生产者一个消费者的设计方式,如果 消费者的速度小于生产者的速度,那么BlockingQueue会阻塞生产者,直到日志线程有能力处理新的日志消息
但是它不支持关闭,我们下面想办法把它关闭。第一种方法是将日志线程修改为当捕获到InterrupterException就退出 。也就是只关闭了消费者
这样有几个缺点,第一个就是会丢失将要处理的日志信息。第二,当其他线程调用log时被阻塞,因为日志消息队列是满的。因为这些线程将无法解除阻塞状态。但在这个示例中,生产者并不是一个专门的线程。因此要取消他们非常困难
第二种关闭logwriter的方法是:设置某个“已请求关闭”的标志,一收到关闭的时候就停止接收日志消息
private boolean shutdownRequested = false;
public void log(String msg) throws InterruptedException {
if (!shutdownRequested) {
queue.put(msg);
} else
throw 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;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (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 block
e.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 block
e.printStackTrace();
}
final LogWriter log = new LogWriter(writer);
log.start();
try {
log.log("sdfds");
log.log("sdfds");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这次把logwriter改写成为原子性的操作。消除了在判断后阻塞的问题。并且引入了一个计数器。确保它可以把已经put的日志消费完,同时不再接收任何日志。这样当消费完毕的时候才会关闭日志服务。