抛出InterruptedException时,不会清除线程中断状态

时间:2018-01-24 10:43:58

标签: java multithreading executorservice

有人可以解释为什么这个程序打印多个“中断”的陈述。

根据Thread.sleep javadoc

  

InterruptedException - 如果有任何线程中断了当前线程。抛出此异常时,将清除当前线程的中断状态。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Test {

    public static void main(String[] args) throws Exception {

        for (int z = 0; z < 100; z++) {

            ExecutorService executor1 = Executors.newSingleThreadExecutor();

            executor1.execute(new Runnable() {

                @Override
                public void run() {

                    ExecutorService executor2 = Executors.newFixedThreadPool(10);

                    for (int i = 0; i < 10; i++) {

                        executor2.execute(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Thread.sleep(1000 * 100);
                                } catch (InterruptedException e) {
                                    if (Thread.currentThread().isInterrupted()) {
                                        System.out.println("interrupted");
                                    }
                                }
                            }
                        });
                    }

                    executor2.shutdownNow();
                    try {
                        executor2.awaitTermination(5, TimeUnit.SECONDS);
                    } catch (InterruptedException e) {
                    }
                }
            });

            executor1.shutdown();

            try {
                executor1.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
            }
        }
    }
}

output - screenshot

3 个答案:

答案 0 :(得分:2)

来自doc

  

如果在调用wait()时阻塞了这个线程,请等待(long),   或者等待(long,int)Object类或join()的方法,   join(long),join(long,int),sleep(long)或sleep(long,int)方法   这个类,然后它的中断状态将被清除   收到InterruptedException。

Thread.sleep(1000 * 100)期间关闭执行程序服务并且InterruptedException被抛出时,状态将被清除。

但ThreadPoolExecutor通过t.interrupt()重新设置状态,因此您仍然可以获得状态。

以下是重置状态的一部分代码:

    for (Worker w : workers) {
        Thread t = w.thread;
        if (!t.isInterrupted() && w.tryLock()) {
            try {
                t.interrupt();
            } catch (SecurityException ignore) {
            } finally {
                w.unlock();
            }
        }
        if (onlyOne)
            break;
    }

答案 1 :(得分:1)

这个答案是错的(保持原样,以便人们知道这个代码片段不是原因)

Executors.newFixedThreadPool()方法返回ThreadPoolExecutor

如果你看ThreadPoolExecutor.shutdownNow(),你会发现它可能会两次打断工人 -

public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers(); //First interrupt here
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate(); //Second interrupt here
        return tasks;
    }

所以必须有些线程被中断两次。

他们第一次被打断,他们会从睡眠状态出来并抛出InterruptedException。但是在他们这样做之后,他们又被打断了(在执行你的代码之前)

答案 2 :(得分:1)

简而言之:

您会看到此行为,因为此处有竞争条件。有一个字段ReentrantLock ThreadPoolExecutor::mainLock。方法shutdownNow由它保护,但启动工作线程不是。

深入挖掘:

看看ThreadPoolExecutor::execute(Runnable)。这是片段:

if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
    if (! isRunning(recheck) && remove(command))
        reject(command);
    else if (workerCountOf(recheck) == 0)
        addWorker(null, false);  //add the worker to worker queue and start the thread
}

现在,让我们来看看工人是如何开始的。这是runWorker方法片段(有些评论是我的):

// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted.  This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
        (Thread.interrupted() &&
        runStateAtLeast(ctl.get(), STOP))) &&
        !wt.isInterrupted()) //Was not interrupted yet by the shutdownNow method, 
                             //but the pool is being stopped
    wt.interrupt(); //<-- Interrupt it before running. 
//...
try {
      task.run();
}
//...

在这种情况下,关闭执行程序并执行任务很有效。

更具体地说,工作线程在 开始执行任务之前 中断 工作线程是 ExecutorService::shutdownNow方法中断,但 后,启动了关机。

如果您运气不好,在条件

之前shutdownNow方法正在更新中断状态
if (Thread.currentThread().isInterrupted())

是checke,但在调用Thread.sleep(1000 * 100);后立即抛出(因为已经中断)。

UPD :我在调用System.out.println(Thread.currentThread().isInterrupted());之前插入了Thread.sleep(1000 * 100);,现在我无法重现案例。

我可以猜到,在调用Thread::sleep之前,方法ExecutorService::shutdownNow会中断所有线程。

注意: 可能存在相同行为的其他原因,但是根据我运行的测试来判断这个似乎是最有可能的。