最近在看线程中断的知识。不知道是不是我理解有问题还是代码有点问题。求大佬看看
第五章 5-18 通用的代码优雅停止办法的实例
public class TerminatableTaskRunner implements TaskRunnerSpec {
protected final BlockingQueue<Runnable> channel;
// 线程停止标记
protected volatile boolean inUse = true;
// 待处理任务计数器
public final AtomicInteger reservations = new AtomicInteger(0);
private volatile Thread workerThread;
public TerminatableTaskRunner(BlockingQueue<Runnable> channel) {
this.channel = channel;
this.workerThread = new WorkerThread();
}
public TerminatableTaskRunner() {
this(new LinkedBlockingQueue<Runnable>());
}
@Override
public void init() {
final Thread t = workerThread;
if (null != t) {
t.start();
}
}
@Override
public void submit(Runnable task) throws InterruptedException {
channel.put(task);
reservations.incrementAndGet();
}
public void shutdown() {
Debug.info("Shutting down service...");
inUse = false;// 语句①
final Thread t = workerThread;
if (null != t) {
t.interrupt();// 语句②
}
}
public void cancelTask() {
Debug.info("Canceling in progress task...");
workerThread.interrupt();
}
class WorkerThread extends Thread {
@Override
public void run() {
Runnable task = null;
try {
for (;;) {
// 线程不再被需要,且无待处理任务
if (!inUse && reservations.get() <= 0) {// 语句③
break;
}
task = channel.take();
try {
task.run(); //---语句五---真正的任务逻辑
} catch (Throwable e) {
e.printStackTrace();
}
// 使待处理任务数减少1
reservations.decrementAndGet();// 语句④
}// for循环结束
} catch (InterruptedException e) {
workerThread = null;
}
Debug.info("worker thread terminated.");
}// run方法结束
}// WorkerThread结束
}
这段代码讲的是让工作线程停止的。
生产者不断submit任务。消费者自旋消费阻塞队列里面的任务。
书上说中断标示位容易被task.run()(语句五)方法吞没掉(即其他线程调用workerThread.interrupt(),如果此时刚好在执行语句五,语句五抛Interrupt异常,中断标示位被清除,下一轮循环到take被不会被中断了。所以采用了inUse变量和reservations变量来解决中断标示位可能被清除的问题。
inUse变量是没问题,感觉加了reservations有点多余,甚至会导致在有种情况线程中断不了。
比如现在任务有3个(即reservations.get()=3) 。代码执行到语句五task.run()的时候,其他线程调用了shutdown()方法。语句五的run方法抛出InterruptException并清除了标记位置。这时候只能依赖inUse和reservations来break这个for循环让线程退出了。单单用inUse方法(即只判断!inUse就退出)本来是下次循环就能break了。但是如果加上reservations.get()<= 0 这个条件可能会出现一些问题。就是这个消费线程的消费速度(即执行语句五task.run()的速度)远不如其他生产线程的submit任务速度。这就会导致这个for循环会一直执行一下,导致线程永远无法退出。
我想到的方法是
一、在submit任务加上限制,如果inUse为true,才让put任务进阻塞队列里面。这样会在我上述提到可能出现的问题下,工作者线程会执行完所有队列里面的任务,然后再从break退出。
@Override
public void submit(Runnable task) throws InterruptedException {
if(inUse){
channel.put(task);
reservations.incrementAndGet();
}
}
二、解决语句五可能出现的中断标记被清除的问题,即重新标记,让下次循环直接从channel.take()退出
try {
task.run(); //---语句五---真正的任务逻辑
} catch (InterruptedException e) {
this.interrupt(); // 把语句五可能清除的中断标记重新设置为true
} catch (Throwable e) {
e.printStackTrace();
}
不知道我的理解的对不对。。。求大佬指出一下问题,跪谢。