`
coolxing
  • 浏览: 874814 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
9a45b66b-c585-3a35-8680-2e466b75e3f8
Java Concurre...
浏览量:97531
社区版块
存档分类
最新评论

停止基于线程的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);
		}
	}
}
2
2
分享到:
评论

相关推荐

    php_redis-5.2.1-7.2-ts-vc15-x86.zip

    本文将围绕“php_redis-5.2.1-7.2-ts-vc15-x86.zip”这个压缩包文件,详细讲解如何在Windows环境下安装和使用PHP Redis扩展,以及其关键组件和功能。 首先,从文件名可以看出,这个压缩包是专为PHP 5.2.1到7.2版本...

    php_imagick-3.4.4rc2-7.2-nts-vc15-x64.zip

    这个压缩包"php_imagick-3.4.4rc2-7.2-nts-vc15-x64.zip"是为PHP 7.2版本构建的,非线程安全(NTS)版本,采用Visual C++ 15编译器(VC15),并适用于64位Windows系统。本文将深入探讨PHP Imagick的使用和其中包含的...

    php_redis-5.3.1-7.2-ts-vc15-x86.zip

    标题 "php_redis-5.3.1-7.2-ts-vc15-x86.zip" 提供的信息表明,这是一个针对PHP的Redis扩展包,具体版本为5.3.1,适配PHP 7.2的线程安全(TS)版本,且是为32位(x86)Windows系统设计的。此扩展由Visual C++ 15...

    php_imagick-3.4.4-7.2-nts-vc15-x64.zip

    1. **下载匹配的版本**:标题中提到的"php_imagick-3.4.4-7.2-nts-vc15-x64.zip"是专门为PHP 7.2的64位非线程安全(NTS)版本设计的。确保你已正确选择与你的PHP环境匹配的版本。 2. **解压并复制文件**:解压缩...

    嵌入式实时操作系统的多线程计算--基于ThreadX和ARM--随书光盘(自己备份)

    Express Logic's ThreadX for Win32 Demo Using Visual C/C++ This demo program is intended for use with the book titled "Real-Time Embedded Multithreading: Using ThreadX and ARM" by Edward L....

    php-7.2.34-nts-Win32-VC15-x64.zip

    标题 "php-7.2.34-nts-Win32-VC15-x64.zip" 提供的信息是关于一个适用于Windows操作系统的PHP环境包,具体为版本7.2.34,非线程安全(NTS)版本,且是64位编译,由Visual C++ 15(VS2017)编译器构建。 PHP...

    BANDIZIP-SETUP-STD-ALL_7.2.0.1.zip

    同时,Bandzip的多线程技术使得它在处理大文件或者批量文件时,能充分利用多核处理器的优势,极大地提高了工作效率。 其次,Bandzip的界面设计简洁,用户友好。它没有广告插件,提供了一个清爽的工作环境,让用户...

    php_redis-php7.2-nts-vc15-x86

    标题 "php_redis-php7.2-nts-vc15-x86" 指的是一个针对PHP 7.2版本的非线程安全(NTS)版本的Redis扩展,使用Visual C++ 15编译器(对应于Visual Studio 2017),适用于32位(x86)系统。这个扩展允许PHP应用程序与...

    Java多线程与线程安全实践-基于Http协议的断点续传

    Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与...

    JAVA多线程与线程安全实践-基于Http协议的断点续传.rar

    JAVA多线程与线程安全实践-基于Http协议的断点续传 JAVA多线程与线程安全实践-基于Http协议的断点续传 JAVA多线程与线程安全实践-基于Http协议的断点续传 JAVA多线程与线程安全实践-基于Http协议的断点续传 JAVA多...

    qtcreator-gdb-7.2-mingw-x86

    "qtcreator-gdb-7.2-mingw-x86"这个压缩包文件,如其名所示,是专门为在Windows x86平台上使用MinGW编译器的QT Creator设计的GDB 7.2版本。GDB 7.2是一个较旧但仍然稳定和功能丰富的调试版本,它与QT Creator结合...

    基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip

    基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip基于Java多线程与线程安全实践-基于Http协议的断点续传设计与实现.zip基于...

    php-7.2.29-nts-Win32-VC15-x64.zip

    标题中的"php-7.2.29-nts-Win32-VC15-x64.zip"标识了这是一个PHP的特定版本,具体是7.2.29,适用于Windows 64位操作系统。"Win32"在这里可能是个误写,因为"VC15"通常代表Visual C++ 2017编译器,它主要支持x64架构...

    Java多线程与线程安全实践-基于Http协议的断点续传.zip

    Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与线程安全实践-基于Http协议的断点续传Java多线程与...

    Java多线程与线程安全实践-基于Http协议的断点续传.rar

    Java多线程与线程安全实践-基于Http协议的断点续传.rarJava多线程与线程安全实践-基于Http协议的断点续传.rarJava多线程与线程安全实践-基于Http协议的断点续传.rarJava多线程与线程安全实践-基于Http协议的断点续传...

    apollo-adminservice-1.8.0-github.zip

    `apollo-adminservice-1.8.0-github.zip`是一个包含Apollo AdminService的1.8.0版本源代码和相关资源的压缩包,可以从GitHub获取。 首先,我们来看看`apollo-adminservice.conf`,这是Apollo AdminService的配置...

    php_redis-4.0.0-7.2-nts-vc15-x86.zip

    【标题】"php_redis-4.0.0-7.2-nts-vc15-x86.zip" 是一个PHP扩展包,专为PHP 7.2版本设计,用于支持Redis数据库的交互。"nts"代表"Non Thread Safe",意味着这个版本的扩展不适用于多线程环境,而适合于单线程或者...

    php_redis-3.1.4-7.2-nts-vc15-x86最新版,绝对可用

    标题 "php_redis-3.1.4-7.2-nts-vc15-x86最新版,绝对可用" 指的是一个针对PHP的Redis扩展包,版本为3.1.4,适用于PHP 7.2环境,是非线程安全(nts)版本,采用Visual C++ 15编译器构建,适用于x86架构的Windows系统...

    php-7.2.30-nts-Win32-VC15-x64.zip

    【标题】"php-7.2.30-nts-Win32-VC15-x64.zip" 是一个PHP的Windows平台版本的压缩包,主要用于在本地环境中升级PHP到7.2.30版。这个版本是为64位操作系统设计的,由Visual C++ 15编译器(VC15)构建,并且是非线程...

Global site tag (gtag.js) - Google Analytics