Добро пожаловать в StackOverflow!
Простой способ добиться того, о чем вы просите, - расписание команда сканирования и ожидание возвращения соответствующего Future
результат или выдать исключение во время ожидания простоя в течение некоторого времени. команда сканирования Я имею в виду Callable
, который будет сканировать следующую строку из System.in
.
В этом случае вы выиграли не нужно обрабатывать сложные многопоточность с помощью Thread
s ручной работы. Просто создайте подходящий ExecutorService
(используя соответствующий вызов метода stati c из класса Executors
) для планирования команд. ExecutorService
подобен планировщику Thread
s, ie пулу Thread
s, который обрабатывает их продолжительность жизни и отвечает, например, за их создание и запуск.
Future
- это интерфейс, экземпляр которого позволяет отслеживать время выполнения задачи (например, Thread
), ie проверять, завершено ли, отменять его и т. Д. c ... Callable
интерфейс, реализации которого просто генерируют / возвращают результат после вычисления / вызова метода, или выдают Exception
в случае, если они не могут произвести результат. Future
в нашем контексте будет возвращено командами планирования ExecutorService
, чтобы позволить нам отслеживать срок службы отправленных Callable
s ...
Callable
s, к которым мы собираемся submit просто вернет результат вызова метода Scanner.nextLine
. Отправляя Callable
в Scehduler, мы получаем Future
, который позволяет ожидать завершения Callable
в течение заданного периода времени. Чтобы бесконечно ждать завершения Callable
, мы используем метод get
. Чтобы ждать до определенного c тайм-аута (который мы и ищем), мы используем другой метод get
, предоставляя ему количество времени, которое мы хотели бы ждать.
Существует несколько типов планировщиков (ie ExecutorService
s), которые мы можем создать в Java 8 (которые я использую, как вы можете заметить по ссылкам) и выше, с помощью помощника Executors
class (мы можем создать их, создавая экземпляры соответствующих классов, но для простоты мы будем использовать Executors
'stati c методы). Я не специалист по этим вопросам, но, вообще говоря, есть фиксированный пул потоков , который позволяет максимально заданному количеству Thread
с запускаться в любой момент времени, есть запланированный поток пул , который может выполнять Thread
с с временными ставками и периодами, существует однопотоковая их версия (ie та же концепция, только одна Thread
за раз), есть это кешированный пул потоков , который создает Thread
s по мере необходимости и повторно использует существующие готовые, и, наконец, есть пул рабочих краж * , в котором все его потоки блокируются / ожидают параллельно для работать (я не уверен насчет последнего, но в соответствии с документами может быть полезно, когда ваши задачи порождают другие задачи и т. д.).
Поскольку мы отправляем по одному Callable
за один раз (один Scanner.nextLine
вызов за раз) мы можем использовать однопотоковые версии. А поскольку нам не нужно периодически выполнять отправленные Callable
, но вместо этого мы хотим отправлять их после каждого завершения, мы будем использовать фиксированный однопотоковый пул версии.
Вам также не нужно запускать другой поток, когда пользовательский ввод готов к обработке, но вы можете использовать тот же поток, который отправил Callable
s. Это основной поток в следующем концептуальном коде:
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();
}
}
}
Просто введите слово "стоп" в качестве ввода, и основной поток продолжит обработку ввода связанного пользователя. Или, в качестве альтернативы, вы можете подождать 10 секунд, и будет выдан TimeoutException
, снова продолжая обработку ввода объединенного пользователя.
Я предоставляю вызов метода Executors
'с помощью ThreadFactory
. ThreadFactory
- это просто интерфейс, реализации которого создают Thread
с для данных Runnable
с. Runnable
- это еще раз интерфейс, который на этот раз определяет единственный метод (run
), который выполняет вычисления. В нашем случае это вычисление создается внутри ExecutorService
для хранения ссылки на результат отправленного нами Callable
, чтобы сделать его доступным для get
методов возвращенного Future
, что в свою очередь, сделать его доступным для кода клиента. Этот ThreadFactory
, которым я поставляю ExecutorService
, создает каждый Thread
как демон . Daemon Thread
s не останавливает завершение программы. Когда все потоки, не являющиеся daemon , завершаются, программа завершается независимо от того, работают ли еще какие-либо ( daemon ) потоки.
Таким образом, это происходит вплоть до проблемы, с которой я столкнулся при создании кода: если пользовательский ввод останавливается по таймауту вместо того, чтобы давать слово «стоп» в качестве ввода, это означает, что отправленный нами Callable
еще не завершен. Отправленный нами Callable
ожидает ввода от System.in
. Так что поток будет работать бесконечно или пока пользователь не введет что-то. Если созданные Thread
не были daemon , это не позволило бы программе завершиться. Вот почему я делаю это daemon .
Но что, если после тайм-аута вы захотите продолжить чтение из System.in
с (или без) созданным объектом Scanner
? Тогда вам придется сначала сохранить ссылку на последний Future
, возвращаемый последним вызовом метода ExecutorService.submit
.
Так вот почему у меня есть другая версия, которая полностью передает сканирование другому wrapper объект с именем TimedCallable
, который вы должны использовать для каждого сканирования. Даже после истечения времени ожидания или окончания действия слова «стоп» вы должны продолжать использовать его для сканирования 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 и зарегистрировать соответствующие объекты слушателя.