Java并发编程实践读书笔记(4)任务取消和关闭
任务的取消
中断传递原理
Java中没有抢占式中断,就是武力让线程直接中断。
Java中的中断可以理解为就是一种简单的消息机制。某个线程可以向其他线程发送消息,告诉你“你应该中断了”。收到这条消息的线程可以根据这个消息做出反应。
意思是,不是你说让我停我就会停,我愿意停就停!
中断消息的传递其实就是通过Thread的一个布尔类型中断状态变量实现的。
发送中断请求时,线程的中断变量被设置为true:
Thread t = new Thread(new Runnable() {
@Override
public void run() {
//...
}
});
t.start();
//...
t.interrupt();
任务线程中监听这个状态变量就知道是否有中断请求:
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()) { }
}
或:
@Override
public void run() {
while(!Thread.interrupted()) { }
}
注意:阻塞方法一般都会抛出InterruptedException,这个异常一旦被try-catch到了,Thread的interrupt状态就会被自动清除(设置为false)。如果不得不try-catch(比如在Runnable的run方法中,或其他类似接口方法中),但是又不知道应该怎么处理中断异常时,一定要记得恢复中断状态:
Thread.currentThread().interrupt();//重新标记为已中断
这样做的目的,是为了其他能够真的处理这个中断的地方能收到中断请求!
反过来说就是,把InterruptedException异常try-cath到了,但是又什么都不做,就像是拦截了别人的信件,自己看完之后扔垃圾桶了。这种行为破坏了导致中断机制。
Future的cancel
Future的cancel包含两层意思,一个是如果任务还没启动那么就别启动了,另外一个意思是启动了就中断。
try {
future.get(5, TimeUnit.SECONDS);
} catch (ExecutionException | TimeoutException e) {
e.printStackTrace();
}finally {
future.cancel(true);
}
处理不可中断的任务
JDK中很多阻塞方法都会考虑到中断,对于不可中断的任务,比如Socket连接,我们能做的是改写Thread的interrupt,来手动做一些事情让任务“中断”掉。
public class ReaderThread extends Thread{ private final Socket socket;
private final InputStream in; public ReaderThread(Socket socket) throws IOException {
super();
this.socket = socket;
this.in = socket.getInputStream();
} @Override
public void interrupt() {
try {
//关闭socket,与后面的try-catch配合使用
this.socket.close();
}catch(Exception e) {
}finally{
super.interrupt();
}
} @Override
public void run() {
byte[] buf = new byte[1024];
try {
while(true) {
int count = in.read(buf);//依赖socket.close异常跳出while
if(count<0) {
break;
}else if(count>0) {
processBuffer(buf,count);
}
}
}catch(Exception e) { }
} private void processBuffer(byte[] buf, int count) { } }
由此应该知道,在考虑中断的功能时,可以依赖Java的中断消息机制,但更多的是要根据实际的业务需求来合理利用中断机制。
线程服务的取消
如果我们对外提供了线程服务,那么就应该提供生命周期的方法,比如shutdown、cancel等。
基于前面提到的知识,可以用中断机制来关闭任务线程。但更重要的是线程的关闭不能影响业务。
比如WebLog应用,WebLog使用了一个阻塞队列来缓存WebLog日志,这样对个线程就能同时打日志了,另外有日志处理线程专门负责从队列中取日志来进行记录。这样一个简单的应用在取消的时候就不能盲目的“kill”掉,需要考虑:
1,关闭后已经在阻塞队列中的日志还是应该继续处理完;
2,不能继续接受新的日志存入到队列中了
/**
* @author huqiao
*/
public class LogService { private final BlockingQueue<String> queue;
private final LoggerThread loggerThread;
private final PrintWriter writer;
private boolean isStoped = false;
private int logCount = 0; public LogService() {
queue = new ArrayBlockingQueue<>(10);
loggerThread = new LoggerThread();
writer = new PrintWriter(System.out);
}
public void start() {
loggerThread.start();
} public void stop() {
isStoped = true;
loggerThread.interrupt();//实现queue.take()抛异常的效果,促使任务线程赶紧去查看“isStoped”状态
}
public void log(String log) throws InterruptedException {
synchronized (this) {
if(isStoped) {
//拒绝新日志写入
throw new IllegalStateException(" logger is stoped");
}
logCount++;
}
queue.put(log);
} private class LoggerThread extends Thread{ @Override
public void run() {
try {
while(true) {
try {
String log = null;
synchronized (LogService.this) {
if(isStoped && logCount == 0) {//logCount确保在stop之后还有机会处理接下来的日志
break;
}
}
log = queue.take();
synchronized (LogService.this) {
logCount--;
}
writer.write(log);
} catch (InterruptedException e) {
//忽略异常(try-catch到之后,中断状态已经被标记为false了,再take时就不会出错了),因为已经自己实现了中断机制
}
}
}finally {
writer.close();
}
}
} }
ExecutorService的关闭
shutdown:仅仅表示ExecutorService不再接受新的提交请求,它不会给子任务发中断请求;
shutdownNow:会尝试给任务线程发送中断请求,但如果子线程没有实现中断请求,那发了也没用,子线程照样一直跑;
无论是shutdown还是shutdownNow,如果线程任务没有实现中断,那就根本不会停止。showdownNow会多返回一个List,里面存着还没执行的任务。所以在实现ExecutorService的关闭时,一定要有下面任务线程的配合。
public class ExecutorShutdownTest { public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(1);
for(int i= 0;i<1;i++) {
executorService.execute(new Task(i+""));
} List<Runnable> taskList = executorService.shutdownNow();
boolean successToTerminated = executorService.awaitTermination(10, TimeUnit.SECONDS);//这里等待10秒,能正常等待到任务线程退出
System.out.println("successToTerminated=" + successToTerminated);
for(Runnable runnable : taskList) {
Task task = (Task)runnable;
System.out.println(task.getName());
}
} private static class Task implements Runnable{ private String name;
public String getName() {
return name;
}
public Task(String name) {
this.name = name;
}
@Override
public void run() {
int i = 0;
while(true) {
try {
Thread.sleep(1000);
System.out.println("task " + name + " is running...");
if(++i>3) {//3秒之后正常退出
break;
}
} catch (InterruptedException e) {
//e.printStackTrace();
}
}
} } }
毒丸对象
书中介绍的这个毒丸对象是一种关闭子线程的巧妙办法。还是以前面的日志服务为例,客户代码想要子任务代码不要在继续跑了,那么可以给子任务扔一个“特殊的消息”。然后子任务会识别这个特殊的消息,让自己退出运行。
处理非正常的线程终止
线程服务中的多个线程有可能异常终止,我们应该要能即使的知道这样的事情。
在创建ExecutorService的时候可以指定线程的创建工程(ThreadFactory):
public class UnCatchExceptionHandlerTest { public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(10,new MyThreadFactory());
service.execute(new Runnable() {
@Override
public void run() {
int i = 0;
System.out.println(100 / i);
}
});
} }
在这个线程工程中,可以为thread设置UncatchExceptionHandler:
public class MyThreadFactory implements ThreadFactory{ @Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new MyUncatchExceptionHandler());
return t;
} }
public class MyUncatchExceptionHandler implements UncaughtExceptionHandler{ @Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t.getName() + " occur exception:" + e.getMessage());
} }
注意,这个方法只对execute提交的任务有效,用submit提交的任务,其异常还是会被Future.get()封装在ExecutionException中重新抛出。
Java并发编程实践读书笔记(4)任务取消和关闭的更多相关文章
- Java并发编程实践读书笔记(5) 线程池的使用
Executor与Task的耦合性 1,除非线程池很非常大,否则一个Task不要依赖同一个线程服务中的另外一个Task,因为这样容易造成死锁: 2,线程的执行是并行的,所以在设计Task的时候要考虑到 ...
- Java并发编程实践(读书笔记) 任务执行(未完)
任务的定义 大多数并发程序都是围绕任务进行管理的.任务就是抽象和离散的工作单元. 任务的执行策略 1.顺序的执行任务 这种策略的特点是一般只有按顺序处理到来的任务.一次只能处理一个任务,后来其它任 ...
- Java并发编程实践读书笔记(2)多线程基础组件
同步容器 同步容器是指那些对所有的操作都进行加锁(synchronize)的容器.比如Vector.HashTable和Collections.synchronizedXXX返回系列对象: 可以看到, ...
- Java并发编程实践读书笔记(1)线程安全性和对象的共享
2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...
- Java并发编程实践读书笔记(3)任务执行
类似于Web服务器这种多任务情况时,不可能只用一个线程来对外提供服务.这样效率和吞吐量都太低. 但是也不能来一个请求就创建一个线程,因为创建线程的成本很高,系统能创建的线程数量是有限的. 于是Exec ...
- Java并发编程实战 读书笔记(二)
关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...
- 《Java并发编程实战》第七章 取消与关闭 读书笔记
Java没有提供不论什么机制来安全地(抢占式方法)终止线程,尽管Thread.stop和suspend等方法提供了这种机制,可是因为存在着一些严重的缺陷,因此应该避免使用. 但它提供了中断In ...
- Java并发编程实战 读书笔记(一)
最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一 线程与进程 进程与线程的解释 个人觉 ...
- Java并发编程艺术读书笔记
1.多线程在CPU切换过程中,由于需要保存线程之前状态和加载新线程状态,成为上下文切换,上下文切换会造成消耗系统内存.所以,可合理控制线程数量. 如何控制: (1)使用ps -ef|grep appn ...
随机推荐
- 【JAVA】通过HttpURLConnection 上传和下载文件(二)
HttpURLConnection文件上传 HttpURLConnection采用模拟浏览器上传的数据格式,上传给服务器 上传代码如下: package com.util; import java.i ...
- 前端学习—React—初出茅庐
React学习—初出茅庐 对与React的学习思路,首先React中用到了Class.let.const.以及modual(模块)的导入(import)和导出(export),而这些都是ECMAScr ...
- Django入门与实践-第15章:用户注销(完结)
# myproject/settings.py LOGOUT_REDIRECT_URL = 'home' http://127.0.0.1:8000/logout/ # myproject/urls. ...
- flask_hello world
对于flask框架的学习全部借鉴于http://www.pythondoc.com/flask-mega-tutorial/index.html 在学习的过程中,我使用的是Pycharm IDE,Py ...
- 如何在Linux中统计一个进程的线程数(转)
方法一: /proc proc 伪文件系统,它驻留在 /proc 目录,这是最简单的方法来查看任何活动进程的线程数. /proc 目录以可读文本文件形式输出,提供现有进程和系统硬件相关的信息如 CPU ...
- Lucene原理一
Lucene 是一个高效的,基于Java 的全文检索库. 所以在了解Lucene之前要费一番工夫了解一下全文检索. 那么什么叫做全文检索呢?这要从我们生活中的数据说起. 我们生活中的数据总体分为两种: ...
- .Net socket服务器编程之为何也高效
说到Socket编程,肯定大部分人举手c,c++.可惜现在已没有机会去追随并达到写服务器的水平,所以将就下还是考虑c#版的Socket服务器吧. 经过一番查询,试用.一些数据和事实还是浮出水面,同时对 ...
- Android-系统解析AndroidManifest
在上一篇博客,Android-XML格式描述,介绍来XML在Android中的格式: Android-系统解析AndroidManifest,是分析Android系统是如何去解析AndroidMani ...
- [Linux]几个armhf的ubuntu源
摘自百度贴吧,留存 http://mirrors.ustc.edu.cn/ubuntu-ports/http://ftp.ubuntu-tw.org/mirror/ubuntu-ports/http: ...
- Leader Election 选举算法
今天讲一讲分布式系统中必不可少的选举算法. leader 就是一堆服务器中的协调者,某一个时刻只能有一个leader且所有服务器都承认这个leader. leader election就是在一组进程中 ...