使用线程池本身删除或终止任务,有一个必须的前提:
任务必须存在于队列之中
。
为什么这么说?
是因为我们所谓的“删除任务”是指ThreadPoolExecutor的remove方法:
public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);
tryTerminate(); // In case SHUTDOWN and now empty
return removed;
}
可以看到,是需要从
workQueue
中移除的;
也就是说,
如果已经运行的任务,理论上是无法使用线程池进行删除操作的
。
比如:
private static void testNormalRemove() {
ThreadPoolExecutor tpe = new ThreadPoolExecutor(1, 3, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3));
Thread task1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("normal1," + Thread.currentThread().getId());
}
}
});
Thread task2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
System.out.println("normal2," + Thread.currentThread().getId());
}
}
});
tpe.execute(task1);
tpe.execute(task2);
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tpe.getQueue().size());
System.out.println("remove task2," + tpe.remove(task2));
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
这里有task1、task2两个任务,创建的线程池核心线程为1,那么task1被添加后会被马上运行,而task2会添加入队列等待运行,原理可参考
线程池原理浅析
,那么task2是可以被remove成功的,而task1是不能被remove的,因为
根本不在队列之中
了。
当然,存在一些特殊的线程池,每个任务其实都加入了队列,比如ScheduledThreadPoolExecutor添加任务时,其实每个任务都被添加进了队列:
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
private void delayedExecute(RunnableScheduledFuture<?> task) {
if (isShutdown())
reject(task);
else {
super.getQueue().add(task);
if (isShutdown() &&
!canRunInCurrentRunState(task.isPeriodic()) &&
remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
这样添加进来的任务是可以进行删除操作的;
当然需要注意的是添加的任务被封装成了
RunnableScheduledFuture
,所以删除的时候也要记得转换为RunnableScheduledFuture再进行删除,如果是使用schedule或其他scheduleXX方法,会直接返回RunnableScheduledFuture,可以就返回值来进行删除操作。
而其他线程池则没这么“好运”了,遵循一般规则,仅在超出corePoolSize时才会加入队列;
线程池内部倒是有停止线程的方法,但线程池内部的调度方法大多是private,是无法调用的,比如这个:
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
//略
}
已经运行的线程或者任务都被封装为Worker,但关于Worker的方法并不对外开放,这也是大多线程池无法精细控制线程的原因——当然,线程本身就很说去精细控制。
总体来说,线程一旦启动,就很难通过常规手段去停止,往往需要抛出异常或添加return或者强行interrupt来停止;
如果需要对线程进行一定的掌握,那么使用类似于ScheduledThreadPoolExecutor的线程池在某些场景也不失为一个优良的选择。