1、中断的引入
在程序中可能需要取消操作,也就是任务取消,原因也很多,例如用户请求取消,有时间限制的操作,应用程序事件,错误,关闭等等。
(1)通常,中断是实现取消的最合理的方式
(2)Thread中的中断方法有以下:
public void interrupt(){ }
public boolean isInterrupt(){ } //
public static boolean interrupted(){ } //会清除当前线程的中断状态,并返回它之前的值,这也是清除中断状态唯一的方法
(3)阻塞库方法,例如Thread.sleep和Object.wait等,都会检查何时中断,并且在发现中断时提前返回。它们在响应中断时执行的操作包括:清除中断状态,抛出InterruptedException,表示阻塞操作由于中断而提前结束。
当线程在非阻塞状态下中断时,它的中断状态将被设置,然后根据将被取消的操作来检查中断状态以判断发生了中断。
调用interrupt并不意味着立即停止目标线程正在进行的工作,而只是传递了请求中断的信息
(4)对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。
这句话不是很理解:有些方法,例如wait、sleep和join等,将严格地处理这种请求,当它们收到中断请求或者在开始执行时发现某个已被设置好的中断状态时,将抛出一个异常。设计良好的方法可以完全忽略这种请求,只要它们能使调用代码对中断请求进行某种处理。设计糟糕的方法可能会屏蔽中断请求,从而导致调用栈中的其他代码无法对中断请求作出响应
2、可以通过中断来取消任务,如下代码
import java.math.BigInteger;
import java.util.concurrent.*;
public class PrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted())
queue.put(p = p.nextProbablePrime());
} catch (InterruptedException consumed) {
/* Allow thread to exit */
}
}
public void cancel() {
interrupt();
}
}
3、中断策略
简单地说就是遇到中断后,应该小心地保存中断状态,并且对中断进行处理,可以把中断信息传递给调用者,从而使调用栈中的上层代码可以采取进一步的操作。或者在代码中实现了线程的中断策略,例如恢复中断状态。总之就是不能简单的屏蔽InterruptedException,例如在catch块中捕获到异常却不做任何处理
只有实现了线程中断策略的代码才可以屏蔽中断请求。在常规的任务和库代码中都不应该屏蔽中断请求
4、响应中断
对不可取消的任务在退出前恢复中断,代码可以如下:
import java.util.concurrent.*;
public class NoncancelableTask {
public Task getNextTask(BlockingQueue<Task> queue) {
boolean interrupted = false;
try {
while (true) {
try {
return queue.take();
} catch (InterruptedException e) {
interrupted = true;
// fall through and retry
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
interface Task {
}
}
因为大多数可中断的阻塞方法都会在入口处检查中断状态,并且当发现该状态已被设置时会立即抛出InterruptedException。(通常,可中断的方法会在阻塞或进行重要的工作前首先检查中断,从而尽快地响应中断)
还应该避免在外部线程中安排中断,如下代码,在指定时间内运行一个任意的Runnable。它在调用线程中运行任务,并安排了一个取消任务,在运行指定的时间间隔后中断它。该异常会被timedRun的调用者捕获
import java.util.concurrent.*;
public class TimedRun1 {
private static final ScheduledExecutorService cancelExec = Executors.newScheduledThreadPool(1);
public static void timedRun(Runnable r,
long timeout, TimeUnit unit) {
final Thread taskThread = Thread.currentThread();
cancelExec.schedule(new Runnable() {
public void run() {
taskThread.interrupt();
}
}, timeout, unit);
r.run();
}
}
由于timedRun可以从任意一个线程中调用,因此它无法知道这个调用线程的中断策略,这样的结果一定不好。不要这样做
5、Future来实现取消
通常使用现有库中的类比自行编写更好,而Future拥有cancle方法
import java.util.concurrent.*;
import static net.jcip.examples.LaunderThrowable.launderThrowable;
public class TimedRun {
private static final ExecutorService taskExec = Executors.newCachedThreadPool();
public static void timedRun(Runnable r,
long timeout, TimeUnit unit)
throws InterruptedException {
Future<?> task = taskExec.submit(r);
try {
task.get(timeout, unit);
} catch (TimeoutException e) {
// task will be cancelled below
} catch (ExecutionException e) {
// exception thrown in task; rethrow
throw launderThrowable(e.getCause());
} finally {
// Harmless if task already completed
task.cancel(true); // interrupt if running
}
}
}
6、 对不可中断的阻塞的处理
在Java库中,许多可阻塞的方法都是通过提前返回或抛出InterruptedException来响应中断请求的,然而,并非所有的可阻塞方法或者阻塞机制都能响应中断
如果一个线程由于执行同步的Socket I/O或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有其他任何作用
处理方法:通过改写interrupt方法将非标准的取消操作封装在Thread中
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ReaderThread extends Thread {
private static final int BUFSZ = 512;
private final Socket socket;
private final InputStream in;
public ReaderThread(Socket socket) throws IOException {
this.socket = socket;
this.in = socket.getInputStream();
}
public void interrupt() {
try {
socket.close();
} catch (IOException ignored) {
} finally {
super.interrupt();
}
}
public void run() {
try {
byte[] buf = new byte[BUFSZ];
while (true) {
int count = in.read(buf);
if (count < 0)
break;
else if (count > 0)
processBuffer(buf, count);
}
} catch (IOException e) { /* Allow thread to exit */
}
}
public void processBuffer(byte[] buf, int count) {
}
}
进一步封装这个非标准的取消得到下面的newTaskFor
(CancellableTask中定义了一个CancellableTask接口,该接口扩展了Callable,并增加了一个cancel方法和一个newTask工厂方法来构造RunnableFuture。CanncellingExecutor扩展了ThreadPoolExecutor,并通过改写newTaskFor使得CancellableTask)
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.*;
import net.jcip.annotations.*;
public abstract class SocketUsingTask <T> implements CancellableTask<T> {
@GuardedBy("this") private Socket socket;
protected synchronized void setSocket(Socket s) {
socket = s;
}
public synchronized void cancel() {
try {
if (socket != null)
socket.close();
} catch (IOException ignored) {
}
}
public RunnableFuture<T> newTask() {
return new FutureTask<T>(this) {
public boolean cancel(boolean mayInterruptIfRunning) {
try {
SocketUsingTask.this.cancel();
} finally {
return super.cancel(mayInterruptIfRunning);
}
}
};
}
}
interface CancellableTask <T> extends Callable<T> {
void cancel();
RunnableFuture<T> newTask();
}
@ThreadSafe
class CancellingExecutor extends ThreadPoolExecutor {
public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
if (callable instanceof CancellableTask)
return ((CancellableTask<T>) callable).newTask();
else
return super.newTaskFor(callable);
}
}