关于线程池中终止任务

使用线程池本身删除或终止任务,有一个必须的前提:
任务必须存在于队列之中

为什么这么说?

是因为我们所谓的“删除任务”是指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的线程池在某些场景也不失为一个优良的选择。

猜你喜欢

转载自blog.csdn.net/ifmylove2011/article/details/105792099