如何优雅地停止 运行 阻塞操作的线程?

How to gracefully stop a thread that is running a blocking operation?

我在需要停止的线程中有一个阻塞操作运行:

Future serverFuture = Executors.newCachedThreadPool().submit(() -> {
    var serverSocket = new ServerSocket(Config.SERVER_PORT);

    serverSocket.accept(); // <-- blocking
});

serverFuture.cancel(true);

通常,我会实现一个循环,不断检查线程是否被中断并最终停止它。但是,由于线程在serverSocket.accept()方法中被阻塞,所以这是不可能的。

解决这个问题的常用方法是什么?

对于这种情况,可以使用ServerSocket的close方法中断accept方法。这将在接受调用时阻塞的线程上抛出 SocketException。 (为了能够使用这个,你应该让你的服务器套接字对象在你想取消的地方可访问。)

一般来说,大多数阻塞 api 都有一个可以为阻塞调用指定的超时参数。或者它们可以被执行线程调用的 Thread.interrupt 方法中断。

您需要在调用 accept() 之前在服务器套接字上设置超时 [1]:

serverSocket.setSoTimeout(100);

然后,如果它在设置的超时时间内未能接受任何连接,accept() 将抛出 SocketTimeoutException,您可以使用它来决定是否要 continue/stop 通过中断接受连接从循环或再次调用 accept()

[1] https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html#setSoTimeout(int)

How to gracefully stop a thread that is running a blocking operation?

一般来说,你不能。但是对于某些 特定的 操作,您可以做一些事情。例如,您可以尝试中断线程。在您的例子中,您无法直接访问任务为 运行 的 Thread,您可以使用 Future.cancel():

serverFuture.cancel(true);

但是阻塞操作不一定会在线程中断时被中断。那些做的通常被记录为抛出 InterruptedException,并且不在例外 ServerSocket.accept() 中被声明为抛出。特别是对于该方法,您最好的选择可能涉及使用 ServerSocket.setSoTimeout()accept() 将阻止的时间量设置一个合适的上限。使用相对较短的超时并在循环中执行 accept() 将允许您定期检查是否要中止任务。

总的来说,您需要选择一个适合您要取消的工作的策略,并且您可能需要花费一些设计精力来使该任务完全可以中断。