簡體   English   中英

java中的取消和中斷

[英]Cancellation and Interruption in java

Java Concurrency in Practice 中有關於如何在線程中使用取消和中斷的說明。 此示例位於第 7 章取消和關閉的第 21 頁,其中指出:

清單 7.3。 不可靠的取消會使生產者陷入阻塞操作。 不要這樣做。

他們在這里告訴我們,為了停止任何線程操作,只需創建一個可以檢查的易失性標志。 根據該標志線程執行停止的狀態。

現在有一個程序可以解釋相同的內容。 它在那里工作正常,以下是示例:

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();
    }
}

在上面的代碼中, cancelled是 volatile 標志,我們可以檢查取消檢查,如果為真,線程執行將停止。

但是,如果我們執行上面所做的相同操作,但使用BlockingQueue則存在一些問題。

但是,如果使用這種方法的任務調用阻塞方法(例如BlockingQueue.put()我們可能會遇到更嚴重的問題,該任務可能永遠不會檢查取消標志,因此可能永遠不會終止。

下面程序中的BrokenPrimeProducer說明了這個問題。 生產者線程生成素數並將它們放在阻塞隊列中。 如果生產者領先於消費者,隊列將填滿並且put()將阻塞。 如果在put()被阻塞時消費者試圖取消生產者任務會發生什么? 它可以調用取消設置cancelled標志,但生產者永遠不會檢查該標志,因為它永遠不會從阻塞put() (因為消費者已停止從隊列中檢索素數)。

這是相同的代碼:

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();
        }
    }
}

我無法理解為什么在第二個代碼示例中阻塞 Queue 的情況下取消將不起作用。 有人可以解釋一下嗎?

這是明確的,因為BlockingQueue#put(E)如果需要在放入值的同時會阻塞 該代碼由於處於阻塞狀態而無法再次檢查該標志,因此該標志在任何其他時間都設置為其他值的事實與當前被阻塞的線程無關。

解決該問題的唯一真實方法是中斷線程,這將結束阻塞操作。

使用標志取消時,如果線程恰好開始休眠或等待,則無法使線程退出休眠或等待,而是必須等待休眠時間到期或等待以通知結束。 阻塞意味着使用者線程處於等待狀態,直到某些內容被排入空隊列,或者生產者線程處於等待狀態,直到有空間將某些內容放入完整隊列。 被阻塞的線程永遠不會離開wait方法-就像您在睡眠或等待的行上有一個斷點一樣,並且線程在該行上被凍結,直到睡眠時間到期或直到線程收到通知為止(不會陷入虛假狀態)喚醒)。 線程無法到達檢查標志的行。

使用中斷會通知線程在等待或睡眠時喚醒。 您不能使用標志來做到這一點。

取消標志需要檢查。 中斷立即通知被阻塞的線程拋出InterruptedException ,而僅while循環的下一次迭代將使線程知道它已被更改-也就是說,當線程解除阻塞並繼續時

看到問題了嗎? 該線程將不知道另一個線程是否設置了該標志。 被封鎖了 它不能轉到下一個迭代。

needMorePrimes()在某些條件下返回false ,然后消費者將調用producer.cancel() ,同時生產者將 BlockingQueue 填滿,使其阻塞在queue.put(p = p.nextProbablePrime())並且可以不檢查取消狀態,所以很糟糕。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM