什么是不间断阻塞?

4
考虑来自TIJ第四版的以下代码:
class SleepBlocked implements Runnable {
  public void run() {
    try {
      TimeUnit.SECONDS.sleep(100);
    } catch(InterruptedException e) {
      print("InterruptedException");
    }
    print("Exiting SleepBlocked.run()");
  }
}

class IOBlocked implements Runnable {
  private InputStream in;
  public IOBlocked(InputStream is) { in = is; }
  public void run() {
    try {
      print("Waiting for read():");
      in.read();
    } catch(IOException e) {
      if(Thread.currentThread().isInterrupted()) {
        print("Interrupted from blocked I/O");
      } else {
        throw new RuntimeException(e);
      }
    }
    print("Exiting IOBlocked.run()");
  }
}

class SynchronizedBlocked implements Runnable {
  public synchronized void f() {
    while(true) // Never releases lock
      Thread.yield();
  }
  public SynchronizedBlocked() {
    new Thread() {
      public void run() {
        f(); // Lock acquired by this thread
      }
    }.start();
  }
  public void run() {
    print("Trying to call f()");
    f();
    print("Exiting SynchronizedBlocked.run()");
  }
}

public class Interrupting {
  private static ExecutorService exec =
    Executors.newCachedThreadPool();
  static void test(Runnable r) throws InterruptedException{
    Future<?> f = exec.submit(r);
    TimeUnit.MILLISECONDS.sleep(100);
    print("Interrupting " + r.getClass().getName());
    f.cancel(true); // Interrupts if running
    print("Interrupt sent to " + r.getClass().getName());
  }
  public static void main(String[] args) throws Exception {
    test(new SleepBlocked());
    test(new IOBlocked(System.in));
    test(new SynchronizedBlocked());
    TimeUnit.SECONDS.sleep(3);
    print("Aborting with System.exit(0)");
    System.exit(0); 
  }
}

这里是输出

Interrupting SleepBlocked
InterruptedException
Exiting SleepBlocked.run()
Interrupt sent to SleepBlocked
Waiting for read():
Interrupting IOBlocked
Interrupt sent to IOBlocked
Trying to call f()
Interrupting SynchronizedBlocked
Interrupt sent to SynchronizedBlocked
Aborting with System.exit(0)

在这里,您可以看到所有创建的线程最终都被中断了(至少我认为是这样,因为没有人执行其运行方法到最后),但是在此之后,Bruce Eckel继续说:

您无法中断试图获取同步锁或试图执行I/O操作的任务。

那么这里的中断是否意味着其他的东西?

此外,他指出:

SleepBlock是可中断阻塞的示例,而IOBlocked和SynchronizedBlocked是不可中断阻塞的。

这里的不可中断阻塞是什么意思?有人能具体说明一下两者之间的区别吗?


你认为为什么所有的线程都被中断了?例如,print("Interrupted from blocked I/O")从未显示在你的输出中。 - pvg
@pvg 但是IOBlocked的run()方法从未完成,也就是说它被某种方式中断了,可以称之为突然中断,Bruce Eckel在这里指定了一个术语“不可中断阻塞”。也许这与此有关。 - abhi_awake
如果它从未打印出“Interrupted from blocked I/O”,那么它就永远不会收到InterruptedException。如果它也从未退出,那么很明显它仍然被in.read()阻塞了。这与你的断言相反。 - user207421
为了进一步解释这里的其他回答,不间断阻塞意味着已经阻塞在I/O或同步等操作上的线程将不会响应中断。它将忽略中断并继续阻塞线程,并且不提供任何响应。阻塞方法应该理想地实现中断返回方法并及时通知被阻塞的线程,但由于疏忽、愚蠢或其他原因,I/O无法被中断。如果需要可中断锁定,请使用ReentrantLock。 - user2982130
2个回答

1
他已经过时了,或者你的版本过时了。NIO支持可中断I/O,通过InterruptibleChannel,尽管只是以相当无用的方式关闭通道,由于荒谬的Linux中断语义。 java.iojava.net I/O操作不受中断影响,你提出的问题没有证明相反。如果有影响,你会在输出中看到"Interrupted from blocked I/O",但你没有看到。你的I/O线程仍然在in.read()中被阻塞。

从技术上讲,可以在InterruptibleChannel上覆盖默认的中断行为,并“黑客式”地引入信号机制。实际上,我并不确定它是否适用于Windows,但它在我用来找出这个问题的Linux机器上是有效的。 - user2982130
当我多年前抱怨ClosedByInterruptException语义时,NIO团队向我提出的理由是,由于Linux在中断时关闭FD,因此NIO也必须这样做。@xTrollxDudex - user207421
我的意思是关闭通道是在Java端完成的,如果你代理AbstractInterruptibleChannel中的中断处理程序,则可能(虽然很可能不安全)避免在中断时关闭通道。 - user2982130
@xTrollxDudex 除非 NIO 团队误导了我,否则在 Linux 上无法实现。 - user207421
@xTrollxDudex 中断通道的关闭是在Linux端完成的。Java端所做的只是进行镜像。如果Linux没有这样的行为,整个“ClosedByInterrupt”事件就不会存在。否则,NIO的人误导了我。我只是报告他们告诉我的内容。我自己没有访问Linux的权限。 - user207421
显示剩余2条评论

0

每个线程都有一个中断状态,用作布尔标志。当有人想要中断线程时,该标志设置为true。没有关于中断的精确定义,但通常用作取消的信号。

线程可以在运行和执行某些工作时或在等待/睡眠(处于WAITINGTIMED_WAITING状态)时被中断。

  1. 如果在线程运行时被中断,则仅将中断标志设置为true,不会发生其他任何事情。因此,逻辑上,如果您想要取消作业,则应不断检查此值:
Callable<V> task = () -> {
    while (more work to do) {
        if (Thread.currentThread().isInterrupted()) return null;
        // Do more work
    }
    return result;
};

如果在等待/睡眠状态下被中断,将抛出InterruptedException异常(并清除中断状态,因此如果您在catch块中检查标志,则它将为false)。 这些方法的示例包括:wait(),Thread.sleep(),BlockingQueue.get(),Semaphore.acquire()和Thread.join()。
因此,如果一个方法被称为不可中断的,则可以预期它不会抛出异常,而是将中断标志设置为true并继续运行。 您可以查看使用acquire和acquireUninterruptibly的信号量示例:
static class SemaphoreBlocked implements Runnable {

        Semaphore semaphore = new Semaphore(0);
        @Override
        public void run() {
            try {
                semaphore.acquire();
            } catch (InterruptedException e) {
                log.info("Semaphore interrupted, {}", Thread.currentThread().isInterrupted());
                throw new RuntimeException(e);
            }
        }
    }

static class SemaphoreBlockedUninterruptibly implements Runnable {

        Semaphore semaphore = new Semaphore(0);
        @Override
        public void run() {
            semaphore.acquireUninterruptibly();
            System.out.println("Exiting");
        }
}
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接