Java 并发编程之任务取消(五)

停止基于线程的服务

正确的封装原则是:除非拥有某个线程,否则不能对该线程进行操控。例如中断线程或修改线程的优先级等 。那么什么 是拥有某个线程呢,就是创建该线程的类,一般来说线程池是其工作线程的所有者,所以要修改线程的话,需要使用线程池来执行

线程的所有权是不能传递的。在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的日志消费完,同时不再接收任何日志。这样当消费完毕的时候才会关闭日志服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值