如果在最后 15 秒内 System.in 上没有任何输入,线程将如何超时

How would one time out a thread if there is no input on System.in in the last 15 seconds

我需要让线程监听 system.in,连接所有输入,并且根据某个命令或者如果 10 秒内没有输入,它需要 运行 另一个将使用收集的信息。 需要明确的是,每次输入数据时,我都需要重置 10 秒。 作为并发编程的初学者,我不太确定如何处理这个

欢迎使用 Whosebug!

实现您所要求的一个简单方法是从 System.in.

schedule a scanning command and wait for the corresponding Future to return the result or throw an exception while waiting idle for an amount of time. By scanning command I mean a Callable that will scan the next line

在这种情况下,您将不需要使用手工制作的 Thread 来处理复杂的多线程。只需创建一个合适的 ExecutorService(通过使用来自 Executors class 的适当静态方法调用)来调度命令。 ExecutorService 就像 Threads 的调度程序,即 Threads 的池,它处理它们的生命周期并负责例如创建它们并启动它们。

Future是一个接口,它的一个实例可以让你监控一个执行任务(比如一个Thread)的执行时间,即检查是否完成,取消等等。 . Callable 是一个接口,其实现只是 generating/returning computation/method-call 之后的结果,或者抛出 Exception 以防它们无法产生结果。 Future 在我们的上下文中将被 ExecutorService 的调度命令 return 编辑,让我们监控提交的 Callables 的生命周期...

我们要提交的 Callable 将只是 return Scanner.nextLine 方法调用的结果。通过向 scehduler 提交 Callable,我们将返回一个 Future,它让我们等待 Callable 完成一段给定的时间。要无限期地等待 Callable 的完成,我们使用 get method. To wait for up to a specific timeout (which is what we are looking for) we use the other get 方法,为其提供我们希望等待的时间量。

我们可以在 Java 8(我正在使用,你可以从链接中看出)及更高版本中创建多种类型的调度程序(即 ExecutorServices,通过 Executors helper class(我们也可以通过实例化相应的 classes 来创建它们,但为了简单起见,我们将使用 Executors 的静态方法)。我不是这方面的专家,但一般来说有 固定线程池 ,它最多允许给定数量的 Threads 到 运行 在任何给定的time,有scheduled thread pool,可以按时间速率和周期执行Threads,有单线程版本其中(即相同的概念,一次只有一个 Thread),有 缓存线程池 ,它根据需要创建 Thread 并重用现有的已完成线程,最后是 work stealing pool 它的所有线程 block/wait 并行工作(我不确定最后一个,但根据文档可以当您的任务生成其他任务等时很有用)。

由于我们一次提交一个 Callable(一次一个 Scanner.nextLine 调用),我们可以使用单线程版本。由于我们不关心定期执行提交的 Callable 而是我们希望在每次完成后提交它,那么我们将使用 固定的单线程池 版本。

您也不需要在准备好处理用户输入时启动另一个线程,但您可以使用提交 Callable 的同一个线程。这是以下概念代码中的主线程:

import java.util.LinkedList;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Concept {

    public static void main(final String[] args) {
        final LinkedList<String> q = new LinkedList<>(); //The collection to hold all user's input.
        final Scanner scan = new Scanner(System.in); //The Scanner of the System.in input stream.
        final TimeUnit waitUnit = TimeUnit.SECONDS; //What unit of time should we use when waiting for input.
        final long waitAmount = 10; //How much time (in 'waitUnit' units) should we wait for.

        //An executor with a single (daemon) thread:
        final ExecutorService scheduler = Executors.newSingleThreadExecutor(r -> {
            final Thread t = new Thread(r);
            t.setDaemon(true);
            return t;
        });

        try {
            try {
                //Main loop for reading and waiting:
                for (String input = scheduler.submit(() -> scan.nextLine()).get(waitAmount, waitUnit);
                        !Objects.equals(input, "stop");
                        input = scheduler.submit(() -> scan.nextLine()).get(waitAmount, waitUnit))
                    q.add(input); //Add the user's last input to the collection.

                //If this is reached, then the user entered "stop" as input.
                System.out.println("Ended by user's input.");
            }
            catch (final TimeoutException tx) {
                //If this is reached, then the time timed out when waiting for user's input.
                System.out.println("Ended by timeout.");
            }
            finally {
                //Here you can "consume" however you like all the user's input from the collection:
                q.forEach(line -> System.out.println(line)); //I'm just printing all of it.
            }
        }
        catch (final InterruptedException | ExecutionException x) {
            x.printStackTrace(); //This is where you handle unexpected exceptions.
        }
        finally {
            //Whatever happened, don't forget to shutdown the ExecutorService:
            scheduler.shutdown();
        }
    }
}

只需将单词 "stop" 作为输入,主线程将继续处理串联的用户输入。或者,您可以等待 10 秒,然后将抛出一个 TimeoutException,再次继续处理串联用户的输入。

我正在为 Executors' 方法调用提供 ThreadFactory. A ThreadFactory is simply an interface, implementations of which create Threads for the given Runnables。 Runnable 又是一个接口,它这次定义了一个执行计算的方法 (run)。在我们的例子中,这个计算是在 ExecutorService 内部创建的,用于存储我们提交的 Callable 结果的引用,以便 get 方法可以使用 returned Future,这将使其可供客户端代码使用。这个 ThreadFactory,我提供给 ExecutorService,正在创建每个 Thread 成为一个 daemonDaemon Threads 不会阻止程序终止。当所有非 daemon 线程完成后,程序终止,与其他(daemon)线程是否仍在 运行 无关宁.

因此,这归结为我在创建代码时遇到的问题:如果用户输入因超时而停止,而不是将单词 "stop" 作为输入,这意味着 Callable 我们提交的还没有完成。我们提交的 Callable 正在等待 System.in 的输入。因此该线程将 运行 无限期地,或者直到用户输入某些内容。如果创建的 Thread 不是 守护进程 则程序不会终止。这就是我将其设为 daemon.

的原因

但是,如果在超时后,您想在创建(或不创建)Scanner 对象的情况下继续从 System.in 中读取怎么办?然后,您必须首先维护对最后一个 ExecutorService.submit 方法调用的最后一个 Future return 的引用。

这就是为什么,我有另一个版本,它将扫描完全传递给另一个名为 TimedCallablewrapper 对象,您应该在每次扫描时使用它。即使在超时或以 "stop" 字结束后,您仍应继续使用它来扫描 System.in:

import java.util.LinkedList;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class Main {

    public static class TimedCallable<V> implements Callable<V> {
        private final Callable<V> callable;
        private final ExecutorService scheduler;
        private Future<V> lastFuture;

        public TimedCallable(final Callable<V> callable) {
            this.callable = Objects.requireNonNull(callable);
            scheduler = Executors.newSingleThreadExecutor(r -> {
                final Thread t = new Thread(r);
                t.setDaemon(true); //Needs to be a daemon in order to let the program end.
                return t;
            });
            lastFuture = null;
        }

        @Override
        public synchronized V call() throws InterruptedException, ExecutionException {
            if (lastFuture == null)
                try {
                    return callable.call();
                }
                catch (final Exception x) {
                    throw new ExecutionException(x);
                }
            final V v = lastFuture.get();
            lastFuture = null;
            return v;
        }

        public synchronized V call(final TimeUnit timeoutUnit,
                                   final long timeoutAmount) throws TimeoutException, InterruptedException, ExecutionException {
            if (lastFuture == null)
                lastFuture = scheduler.submit(callable);
            final V v = lastFuture.get(timeoutAmount, timeoutUnit); /*If it throws TimeoutException,
            then the 'lastFuture' property will not be nulled by the following statement:*/
            lastFuture = null;
            return v;
        }
    }

    public static void main(final String[] args) {
        final LinkedList<String> q = new LinkedList<>(); //The collection to hold all user's input.
        final Scanner scan = new Scanner(System.in); //The Scanner of the System.in input stream.
        final TimeUnit waitUnit = TimeUnit.SECONDS; //What unit of time should we use when waiting for input.
        final long waitAmount = 10; //How much time (in 'waitUnit' units) should we wait for.

        //Instantiate the scanner's timed-callable:
        final TimedCallable<String> scanNextLine = new TimedCallable<>(() -> scan.nextLine());

        try {
            try {
                //Main loop for reading and waiting:
                for (String input = scanNextLine.call(waitUnit, waitAmount); !Objects.equals(input, "stop"); input = scanNextLine.call(waitUnit, waitAmount))
                    q.add(input); //Add the user's last input to the collection.

                //If this is reached, then the user entered "stop" as input.
                System.out.println("Ended by user's input.");
            }
            catch (final TimeoutException tx) {
                //If this is reached, then the time timed out when waiting for user's input.
                System.out.println("Ended by timeout.");
            }
            finally {
                //Here you can "consume" however you like all the user's input from the collection:
                q.forEach(line -> System.out.println(line)); //I'm just printing all of it.

                //Keep on using the Scanner via the TimedCallable:
                System.out.println("Enter next line:");
                System.out.println(scanNextLine.call());
                System.out.println("Enter last line:");
                System.out.println(scanNextLine.call());
            }
        }
        catch (final InterruptedException | ExecutionException x) {
            x.printStackTrace(); //This is where you handle unexpected exceptions.
        }
    }
}

最后说明:我在两个版本中都假设用户在输入句子时可能会因超时而被打断。例如,如果您将超时设置为 1 秒,那么用户可能没有足够的时间来输入他想要的内容,直到超时到期并打扰他。为了更好地控制输入过程,您最好创建一个 GUI 并注册相应的侦听器对象。