停止基于线程的Service--JCIP7.2读书笔记

[本文是我对Java Concurrency In Practice 7.2的归纳和总结.  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. ]

以ExecutorService为例, 该类内部封装有多个线程, 类外部无法直接停止这些线程. 相反, 外部调用Service的shutDown和shutDownNow方法关闭Service, 而Service负责停止其拥有的线程.

大多数server应用会使用到log, 下例中的LogWriter是一个使用生产者消费者模式构建的log service, 需要打印log的线程将待打印的内容加入到阻塞队列中, 而logger线程则不断的从阻塞队列中取出数据输出:

public class LogWriter {
	private final BlockingQueue<String> queue;
	private final LoggerThread logger;

	public LogWriter(Writer writer) {
		this.queue = new LinkedBlockingQueue<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 void run() {
			try {
				while (true)
					writer.println(queue.take());
			} catch (InterruptedException ignored) {
			} finally {
				writer.close();
			}
		}
	}
}

LogWriter内部封装有LoggerThread线程, 所以LogWriter是一个基于线程构建的Service. 根据ExecutorService的经验, 我们需要在LogWriter中提供停止LoggerThread线程的方法. 看起来这并不是很难, 我们只需在LogWriter中添加shutDown方法:

/**
 * 该方法用于停止LoggerThread线程
 */
public void shutDown() {
	logger.interrupt();
}

当LogWriter.shutDown方法被调用时, LoggerThread线程的中断标记被设置为true, 之后LoggerThread线程执行queue.take()方法时会抛出InterruptedException异常, 从而使得LoggerThread线程结束.

然而这样的shutDown方法并不是很恰当: 

1. 丢弃了队列中尚未来得及输出的数据.

2. 更严重的是, 假如线程A对LogWriter.log方法的调用因为队列已满而阻塞, 此时停止LoggerThread线程将导致线程A永远阻塞在queue.put方法上.

对上例的改进:

public class LogWriter {
	private final BlockingQueue<String> queue;
	private final LoggerThread loggerThread;
	private final PrintWriter writer;

	/**
	 * 表示是否关闭Service
	 */
	private boolean isShutdown;
	/**
	 * 队列中待处理数据的数量
	 */
	private int reservations;

	public void start() {
		loggerThread.start();
	}

	public void shutDown() {
		synchronized (this) {
			isShutdown = true;
		}
		loggerThread.interrupt();
	}

	public void log(String msg) throws InterruptedException {
		synchronized (this) {
			// service已关闭后调用log方法直接抛出异常
			if (isShutdown)
				throw new IllegalStateException("Service has been shut down");
			++reservations;
		}
		// BlockingQueue本身就是线程安全的, put方法的调用不在同步代码块中
		// 我们只需要保证isShutdown和reservations是线程安全的即可
		queue.put(msg);
	}

	private class LoggerThread extends Thread {
		public void run() {
			try {
				while (true) {
					try {
						synchronized (this) {
							// 当service已关闭且处理完队列中的所有数据时才跳出while循环
							if (isShutdown && reservations == 0)
								break;
						}
						String msg = queue.take();
						synchronized (this) {
							--reservations;
						}
						writer.println(msg);
					} catch (InterruptedException e) {
						// 发生InterruptedException异常时不应该立刻跳出while循环
						// 而应该继续输出log, 直到处理完队列中的所有数据
					}
				}
			} finally {
				writer.close();
			}
		}
	}
}

上面的处理显得过于复杂, 利用ExecutorService可以编写出相对更简洁的程序:

public class LogService {
	/**
	 * 创建只包含单个线程的线程池, 提交给该线程池的任务将以串行的方式逐个运行
	 * 本例中, 此线程用于执行打印log的任务
	 */
	private final ExecutorService exec = Executors.newSingleThreadExecutor();
	private final PrintWriter writer;

	public void start() {
	}

	public void shutdown() throws InterruptedException {
		try {
			// 关闭ExecutorService后再调用其awaitTermination将导致当前线程阻塞, 直到所有已提交的任务执行完毕, 或者发生超时
			exec.shutdown();
			exec.awaitTermination(TIMEOUT, UNIT);
		} finally {
			writer.close();
		}
	}

	public void log(String msg) {
		try {
			// 线程池关闭后再调用其execute方法将抛出RejectedExecutionException异常
			exec.execute(new WriteTask(msg));
		} catch (RejectedExecutionException ignored) {
		}
	}
	
	private final class WriteTask implements Runnable {
		private String msg;
		
		public WriteTask(String msg) {
			this.msg = msg;
		}

		@Override
		public void run() {
			writer.println(msg);
		}
	}
}

你可能感兴趣的:(java,cancel,interrupt,flag)