1、取消

(1)生产者消费者问题中,如果采用BlockingQueue阻塞队列。假如生产者生产的速度超过了消费者的处理速度,队列将被填满,put操作也会被阻塞。当生产者在put方法中阻塞时,如果消费者希望取消生产者任务,它可以通过调用cancel方法来设置cancelled标志,但此时生产者却永远不能检查这个标志,因为它无法从阻塞的方法中恢复过来(消费者已经停止从队列中取出素数,所以put方法将一直保持阻塞状态)。此时任务永远不会结束。

如下面代码:

public class BrokenPrimeProducer extends Thread {

	private final BlockingQueue<BigInteger> queue;
	private volatile boolean cancelled = false;
	static int produceCount = 1;
	static int consumeCount = 1;

	BrokenPrimeProducer(BlockingQueue<BigInteger> queue) {
		this.queue = queue;
	}

	public void run() {
		try {
			BigInteger p = BigInteger.ONE;
			synchronized (BrokenPrimeProducer.class) {
				while (!cancelled) {
					queue.put(p = p.nextProbablePrime());// 生产者生成素数放入队列
					System.out.println("===生产===" + produceCount++);
				}
			}
			System.out.println("!!结束!!");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void cancel() {
		cancelled = true;
		System.out.println("!!请停止生产吧");
	}

	static void consumePrimes() throws InterruptedException {
		BlockingQueue<BigInteger> primes = new LinkedBlockingQueue<>(10);
		BrokenPrimeProducer producer = null;
		for (int i = 0; i < 100000; i++) {
			producer = new BrokenPrimeProducer(primes);
			producer.start();
		}
		try {
			while (needMorePrimes()) {// 仍需要素数时,从中取出
				consume(primes.take());
			}
		} finally {
			producer.cancel();// 取消生产
		}
	}

	private static void consume(BigInteger take) {
		System.out.println("==取出第==" + consumeCount++ + "个素数:" + take);
	}

	private static boolean needMorePrimes() {
		if (consumeCount < 11) {
			return true;
		}
		return false;
	}

	public static void main(String[] args) throws InterruptedException {
		consumePrimes();
	}
}

运行结果:

 

java 如果取消指定的定时任务 java确定和取消按钮_i++

(2)中断取消

在上面示例中,幸运的话有可能会取消,但多数情况是没有办法检测到cancelled标志,一直阻塞。可以采用中断的方法取消。

Thread的中断方法有:

public class Thread{
    public void interrupt(){}//中断线程
    public static boolean interrupted(){}//清除中断状态
    public boolean isInterrupted(){}
}

中断并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己。 

将示例代码做出改动:

public void run() {
		try {
			BigInteger p = BigInteger.ONE;
			synchronized (BrokenPrimeProducer.class) {
				while (!Thread.currentThread().isInterrupted()) {
					queue.put(p = p.nextProbablePrime());// 生产者生成素数放入队列
					System.out.println("===生产===" + produceCount++);

				}
			}
		} catch (InterruptedException e) {
			System.out.println("!!结束!!");
			e.printStackTrace();
		}
	}

	public void cancel() {
		interrupt();
	}

	static void consumePrimes() throws InterruptedException {
		BlockingQueue<BigInteger> primes = new LinkedBlockingQueue<>(10);
		BrokenPrimeProducer producer = null;
		List<BrokenPrimeProducer> producers = new ArrayList<>();
		for (int i = 0; i < 10000; i++) {
			producer = new BrokenPrimeProducer(primes);
			producer.start();
			producers.add(producer);
		}
		try {
			while (needMorePrimes()) {// 仍需要素数时,从中取出
				consume(primes.take());
			}
		} finally {
			for (BrokenPrimeProducer p : producers) {// 如果有多个生产者,需要一个个中断
				p.cancel();// 取消生产
			}
		}
	}

结果:

在每次迭代循环中有两个地方可以检测出中断,一个是put方法调用,一个是循环开始处查询状态时。

java 如果取消指定的定时任务 java确定和取消按钮_java 如果取消指定的定时任务_02

 (3)如果除了将InterruptedException传递给调用者外还要进行额外的操作,则需要在捕获InterruptedException之后恢复中断状态:Thread.currentThread().interrupt()。

在取消过程中可能涉及除了中断状态之外的其他状态,,中断可以用来获得线程的注意,并且由中断线程保存的信息,可以为中断的线程提供进一步的指示。(当访问这些信息时,要保证使用同步)

例如,当一个ThreadPoolExecutor拥有的工作者线程检测到中断时,它会检查线程池是否正在关闭。如果是,它会在结束之前执行一些线程池清理工作,否则它可能创建一个新线程将线程池恢复到合理的规模。

(4)计时运行

java 如果取消指定的定时任务 java确定和取消按钮_System_03

这是一种简单的方法,但却破坏了一下规则:在中断线程之前,应该了解它的中断策略。由于timedRun可以从任意一个线程调用,因此无法知道这个调用线程的中断策略。我的理解是,因为这个方法不知道调用的方法后续的处理,所以如果捕获异常,应该抛回去给调用方处理。改进如下:

java 如果取消指定的定时任务 java确定和取消按钮_线程池_04

 (5)

java 如果取消指定的定时任务 java确定和取消按钮_java 如果取消指定的定时任务_05

(6)可以根据自己的需求来重写中断方法

java 如果取消指定的定时任务 java确定和取消按钮_线程池_06

2、关闭

(1)中断。当取消一个生产者-消费者操作时需要同时取消生产者和消费者。

(2)设置“请求关闭”标志

(3)使用ExecutorService

(4)“毒丸”对象。“Poison Pill”是指一个放在队列上的对象,当得到这个对象时,立即停止。在FIFO队列中,“Poison Pill”将确保消费者在关闭之前首先完成队列中的所有工作,在提交“毒丸”对象之前提交的所有工作都会被处理,而生产者在提交了“毒丸”对象后,将不会再提交任何工作。

注:只有生产者和消费者数量已知的情况下才可以使用毒丸对象。