简体   繁体   中英

Cancellation and Interruption in java

In Java Concurrency in Practice there is explanation about how to use cancellation and interruption in threads. This example is on Page 21 of Chapter 7 Cancellation and Shutdown, which states:

Listing 7.3. Unreliable Cancellation that can Leave Producers Stuck in a Blocking Operation. Don't Do this.

Here they are telling us in order to stop any thread operation just create a volatile flag which can be checked. Depending on the status of that flag thread execution stops.

Now there is one program for explaining same. It works fine there, below is the example:

public class PrimeGenerator implements Runnable {
    @GuardedBy("this")
    private final List<BigInteger> primes = new ArrayList<BigInteger>();
    private volatile boolean cancelled;

    public void run() {
        BigInteger p = BigInteger.ONE;
        while (!cancelled) {
            p = p.nextProbablePrime();
            synchronized (this) {
                primes.add(p);
            }
        }
    }

    public void cancel() {
        cancelled = true;
    }

    public synchronized List<BigInteger> get() {
        return new ArrayList<BigInteger>(primes);
    }

    List<BigInteger> aSecondOfPrimes() throws InterruptedException {
        PrimeGenerator generator = new PrimeGenerator();
        new Thread(generator).start();
        try {
            SECONDS.sleep(1);
        } finally {
            generator.cancel();
        }
        return generator.get();
    }
}

In the above code cancelled is the volatile flag which we can check for the cancellation check and thread execution stops if its true.

But if we do the same operation which we have done above but use BlockingQueue there is some problem.

If, however, a task that uses this approach calls a blocking method such as BlockingQueue.put() we could have a more serious problem the task might never check the cancellation flag and therefore might never terminate.

BrokenPrimeProducer in below program illustrates this problem. The producer thread generates primes and places them on a blocking queue. If the producer gets ahead of the consumer, the queue will fill up and put() will block. What happens if the consumer tries to cancel the producer task while it is blocked in put() ? It can call cancel which will set the cancelled flag but the producer will never check the flag because it will never emerge from the blocking put() (because the consumer has stopped retrieving primes from the queue).

Here is the code for the same:

class BrokenPrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;
    private volatile boolean cancelled = false;

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

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!cancelled) {
                queue.put(p = p.nextProbablePrime());
            }
        } catch (InterruptedException consumed) {
        }
    }

    public void cancel() {
        cancelled = true;
    }


    void consumePrimes() throws InterruptedException {
        BlockingQueue<BigInteger> primes =...;
        BrokenPrimeProducer producer = new BrokenPrimeProducer(primes);
        producer.start();

        try {
            while (needMorePrimes()) {
                consume(primes.take());
            }
        } finally {
            producer.cancel();
        }
    }
}

I am not able to understand why cancellation will not work in case of blocking Queue in second code example. Can someone explain?

This is explicitly because BlockingQueue#put(E) will block if it needs to while placing values inside of it. The code isn't in a position to check the flag again due to it being in a blocked state, so the fact that the flag is set to a different value at any other time is independent of the currently blocked thread.

The only real way to address the issue is to interrupt the thread, which will end the blocking operation.

When using a flag to cancel, there's no way to make the thread quit sleeping or waiting if it happens to have started sleeping or waiting, instead you have to wait for the sleep time to expire or for the wait to be ended with a notification. Blocking means a consumer thread sits in a wait state until something gets enqueued in an empty queue, or a producer thread sits in a wait state until there's room to put something in a full queue. The blocked thread never leaves the wait method -- it's as if you had a breakpoint on the line with the sleep or wait, and the thread is frozenon that line until the sleep time expires or until the thread gets a notification (not getting into spurious wakeups). The thread can't get to the line where it checks the flag.

Using interruption signals the thread to wake up if it is waiting or sleeping. You can't do that with a flag.

Cancellation flags need to be checked. Whereas interruption immediately notifies the thread blocked to throw InterruptedException , only the next iteration of the while loop will the thread know it's been changed - that is, when the thread unblocks and continues .

See the problem? The thread won't know if another thread set the flag. It's blocked. It can't go to the next iteration.

needMorePrimes()在某些条件下返回false ,然后消费者将调用producer.cancel() ,同时生产者将 BlockingQueue 填满,使其阻塞在queue.put(p = p.nextProbablePrime())并且可以不检查取消状态,所以很糟糕。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM