多线程进阶006 之 停止基于线程服务
来源:互联网 发布:加百列和路西法知乎 编辑:程序博客网 时间:2024/05/17 00:16
如果应用程序准备退出,那么这些服务所拥有的线程也需要结束,本节将讲述以下技巧:
- 关闭日志服务
– 不支持关闭的日志服务
– 通过一种不可靠的方式增加关闭操作
– 可靠的取消操作 - 关闭ExecutorService
- 毒丸对象
- 只执行一次的服务
- shutdownNow的局限性
– 在ExecutorService跟踪在关闭之后取消的任务
– 使用TrackingExecutorService来保存未完成的任务
关闭日志服务
不可关闭的日志服务
基于生产者消费者的日志服务
public class LogWriter { private final BlockingQueue<String> queue; private final LoggerThread logger; public LogWriter(Writer writer){ this.queue = new LinkedBlockingQueue<>(); 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) { this.writer = (PrintWriter) writer; } //.... @Override public void run() { try{ while(true){ writer.println(queue.take()); } }catch (InterruptedException e){ //... }finally{ writer.close(); } } }}
为了使像LogWriter这样的服务在软件产品中能发挥实际作用,还需要实现一种终止日志线程的方法,从而避免使JVM无法正常关闭.
不可靠的方式增加关闭服务
如果将日志线程修改为捕获到InterruptedException时退出,那么只需要中断日志线程就能停止服务.
public void log(String msg) throws InterruptedException{ if(!isShutdown()) queue.put(msg); else throw new IllegalStateException("logger is shut down"); } public void shutdown(){ logger.interrupt(); } public boolean isShutdown(){ return !logger.isAlive(); }
这种直接关闭的做法会丢失那些正在等待被写入到日志的信息,不仅如此,其他线程将在调用log时被阻塞,因为日志消息队列是满的,因此这些线程无法解除阻塞状态.
向LogWriter添加可靠的取消操作
为LogWriter提供一个可靠关闭操作的方法是解决竞态条件问题,因而要是日志消息的提操作成为原子操作.然而,我们不希望在消息加入队列时去持有一个锁,因为put方法本身就可以阻塞.
通过原子方式来检查关闭请求,并且有条件地递增一个计数器来保持提交消息的权利.
public class LogService { private final BlockingQueue<String> queue; private final LoggerThread loggerThread; private final PrintWriter writer; private boolean isShutdown; private int reservations; public LogService(Writer writer){ queue = new LinkedBlockingQueue<>(); loggerThread = new LoggerThread(); this.writer = (PrintWriter) writer; isShutdown = false; reservations = 0; } public void start(){ loggerThread.start(); } public void log(String msg) throws InterruptedException{ synchronized (this) { if(isShutdown){ throw new IllegalStateException("logger is shut down"); } ++reservations; } queue.put(msg); } public void stop(){ synchronized (this) { isShutdown = true; } loggerThread.interrupt(); } private class LoggerThread extends Thread{ //.... @Override public void run() { try{ while(true){ synchronized (LogService.this) { if(isShutdown && reservations==0){ break; } } String msg = queue.take(); synchronized (LogService.this) { --reservations; } writer.println(msg); } }catch (InterruptedException e){ //... }finally{ writer.close(); } } }}
关闭ExecutorService
简单的程序可以直接在main函数中启动和关闭全局的Executor,而在复杂的程序中,通常会将ExecutorService封装在某个更高级别的服务中,并且该服务能提供其自己的生命周期的办法.
try{ exec.shutdown(); exec.awaitTermination(TIMEOUT,UNIT);}finally{ writer.close();}
毒丸对象
另一种生产者消费者服务的方式就是使用毒丸对象: 毒丸是指放在队列上的一个对象,其含义是: 当得到这个对象时,立即停止.
public class IndexingService { private static final File POISON = new File(""); private final IndexerThread consumer = new IndexerThread(); private final CrawlerThread producer = new CrawlerThread(); private final BlockingQueue<File> queue; private final File root; public IndexingService(File root) { queue = new LinkedBlockingQueue<>(); this.root = root; } public void start(){ producer.start(); consumer.start(); } public void stop(){ producer.interrupt(); } public void awaitTermination() throws InterruptedException{ consumer.join(); } public class CrawlerThread extends Thread{ @Override public void run() { try{ crawl(root); }catch(InterruptedException e){ /*发生异常*/ }finally{ while(true){ try{ queue.put(POISON); break; }catch(InterruptedException e){ /* 重新尝试 */ } } } } private void crawl(File root) throws InterruptedException{ // ... } } public class IndexerThread extends Thread{ @Override public void run() { try{ while(true){ File file = queue.take(); if(file==POISON){ break; } else IndexFile(file); } }catch(InterruptedException e){ /*发生异常*/ } } private void IndexFile(File file) { //... } }}
只有在生产者消费者的数量都已知的情况下,才可以使用”毒丸”对象.
只执行一次的服务
如果某个方法需要处理一匹任务,并且当所有的任务都处理完后才返回,那么可以通过一个私有的Executor来简化服务的生命周期.
boolean checkEmail(Set<String> hosts,long timeout,TimeUnit unit) throws InterruptedException{ ExecutorService exec = Executors.newCachedThreadPool(); final AtomicBoolean hasNewMail = new AtomicBoolean(false); try{ for(final String host : hosts){ exec.execute(new Runnable(){ @Override public void run() { if(checkMail(host)){ hasNewMail.set(true); } } }); } }finally{ exec.shutdown(); exec.awaitTermination(timeout, unit); } return hasNewMail.get();}protected boolean checkMail(String host) { return false;}
shutdownNow的局限性
通过shutdownNow来强行关闭ExecutorService时,它会尝试取消正在执行的任务,并返回所有已提交但尚未开始的任务,从而我们可以将这些任务写入日志或保存起来以便之后进行处理.但是,它并不会返回正在执行,但是没有执行完毕的任务.
在ExecutorService中跟踪在关闭之后取消的任务
TrackingExecutor可以找出哪些任务已经开始,还没有完成.在所有设计良好的任务中,都会实现这个功能.
public class TrackingExecutor { private final ExecutorService exec; private final Set<Runnable> taskCancelledAtShutdown = Collections.synchronizedSet(new HashSet<Runnable>()); public TrackingExecutor(ExecutorService exec) { this.exec = exec; } public List<Runnable> getCancelledTask(){ if(!exec.isTerminated()){ throw new IllegalStateException(); } return new ArrayList<>(taskCancelledAtShutdown); } public void execute(final Runnable runnable){ exec.execute(new Runnable(){ public void run() { try{ runnable.run(); }finally { if(isShutdown()&&Thread.currentThread().isInterrupted()){ taskCancelledAtShutdown.add(runnable); } } } }); } //将ExecutorService的其他方法委托给exec protected boolean isShutdown() { return exec.isShutdown(); }}
使用TrackingExecutorService来保存未完成的任务
网页爬虫程序的工作通常是无穷无尽的,因此当爬虫程序必须关闭时,我们通常希望保存它的状态,以便稍后重新启动.
public abstract class WebCrawler { private static final String TIMEOUT = null; private static final String UNIT = null; private volatile TrackingExecutor exec; private final Set<URL> urlsToCrawl = new HashSet<URL>(); public synchronized void start(){ exec = new TrackingExecutor(Executors.newCachedThreadPool()); for (URL url : urlsToCrawl) { submitCrawlTask(url); } urlsToCrawl.clear(); } public synchronized void stop(){ try{ saveUncrawled(exec.shutdownNow()); if(exec.awaitTermination(TIMEOUT,UNIT)) saveUncrawled(exec.getCancelledTask()); }finally { exec = null; } } public void submitCrawlTask(URL link) { exec.execute(new CrawlTask(link)); } private void saveUncrawled(List<Runnable> c) { for (Runnable task : c) { urlsToCrawl.add(((CrawlTask) task).getPage()); } } protected abstract List<URL> processPage(URL url); private class CrawlTask implements Runnable{ private final URL url; public CrawlTask(URL url) { this.url = url; } @Override public void run() { for(URL link : processPage(url)){ if(Thread.currentThread().isInterrupted()) return ; submitCrawlTask(link); } } public URL getPage(){ return url; } }}
在TranckingExecutor中存在一个不可避免的竞态条件,从而产生误报问题,一些被认为已取消的任务实际上已经执行完成.这个原因在于,在执行任务最后一条指令以及线程池将任务记录为”结束”的两个时刻之间,线程池可能被关闭.
- 多线程进阶006 之 停止基于线程服务
- 多线程编程:停止基于线程的服务
- java多线程之停止线程
- java多线程之-----停止线程
- Java多线程之停止线程
- 停止java基于线程的服务
- 黑马程序员--多线程(三)之线程停止
- Java多线程之停止一个线程
- java多线程之启动,停止线程
- Java多线程编程核心技术之---停止线程
- java 多线程 停止线程
- 多线程(停止线程)
- 多线程学习-停止线程
- 多线程(停止线程)
- JAVA-多线程-停止线程
- 多线程---停止线程
- java多线程 ---- 停止线程
- Java多线程-停止线程
- c++中测试程序运行时间的方案
- C++静态成员
- golang []byte和string相互转换
- Socket编程实现通信
- Linux 下部署Kettle
- 多线程进阶006 之 停止基于线程服务
- 【颗粒归仓】--java集合
- Django 笔记
- P3628 [APIO2010]特别行动队
- leetcode hard模式专杀之99. Recover Binary Search Tree
- Spark2.0.X源码深度剖析之 SparkEnv
- Gradle 出现unindexed remote maven repositories found异常解决方法
- 在Tomcat中部署多个war包
- hive优化总结