中断扫描器 nextLine()(备选方案)
Interrupt Scanner nextLine() (alternatives)
我有以下主题:
Thread t1 = new Thread() {
@Override
public void run() {
while (!progress.equals(duration)) {
try {
Thread.sleep(1000);
progress = progress.plusSeconds(1);
// synchronized (this) { while (paused) { this.wait(); } }
} catch (InterruptedException e) {
interrupt();
}
}
}
};
t1.start();
我正在尝试实现一项允许用户使用控制台暂停和停止此线程的功能。基本上,这个:
Scanner sc = new Scanner(System.in);
int choice;
while (t1.isAlive()) {
System.out.println("Choose an option:\n1. Pause/Resume\n2. Stop");
choice = Integer.parseInt(sc.nextLine());
// if (choice == 1) { ... } else if (choice == 2) { t1.interrupt() }
// synchronized (t1) { t1.notify(); }
}
我的问题是,一旦 t1
结束,t1.isAlive()
的计算结果为 false,但程序不会退出 while
循环,因为它一直在等待来自用户。我想中断sc.nextLine()
,但我读到这是不可能的,因为线程被阻塞了。我该怎么做?
我尝试了以下方法:
Thread t2;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (t1.isAlive()) {
t2 = new Thread() {
@Override
public void run() {
try {
while (!br.ready())
Thread.sleep(200);
choice = Integer.parseInt(br.readLine());
} catch (InterruptedException e) {
} catch (IOException e) {
}
}
};
t2.start();
}
Supposedly, this should allow me to interrupt t2
,但我一定是做错了什么,因为它一直在打印 Chose an option: 1. Pause/Resume 2. Stop
,所以我无法检查它是否有效。
关键问题是 System.in 的 API 不能保证。 JVM 可以满足完整的 JVM 规范,即使它有一个 System.in
这样,如果它被中断,什么也不会发生 实际上完全不可能中断 System.in, 除了 System.exit
.
然而,幸运的是,大多数 JVM 实现并不是这样工作的:如果你在任何给定线程上设置中断标志,将会发生 3 件事:
任何指定要明确查看 em 的方法都将被中断:这些都是声明为 throws InterruptedException
的方法。如果线程的中断标志被提升,所有这些方法将立即停止等待,降低标志,并通过抛出InterruptedException
的方式return。是的,这意味着如果您首先升起中断标志(someThread.interrupt()
升起标志并且不做任何其他事情;是其他查看它的方法使魔法起作用),然后 调用例如Thread.sleep
,睡眠调用 returns 立即 (通过抛出 InterruptedEx)并且甚至不等待一毫秒。
暂停线程但 不是 明确正确处理它的方法处于不确定状态:这取决于 java 运行时如果有任何事情发生。但是,通常 会发生一些事情。这些方法几乎总是抛出某种检查异常(对于数据库连接,SQLEx
,对于网络、文件和管道操作,IOException
);当前正在等待发送或接收这些事物之一的数据的任何代码都将通过降低标志、中止操作和 returning 通过抛出已检查的异常和一条消息来处理升高的中断标志表示发生了中断。
如果正在执行的代码根本不响应中断标志,那么什么也不会发生:标志保持上升状态,JVM 不会做任何其他事情;中断标志的要点是它刚刚被提升,然后你等待直到线程运行查看它的代码。希望这会很快发生,但不能保证。
这意味着您很可能需要做的就是:
在 T1
- 有某种 AtomicBoolean 对象,一旦作业完成,t1 就会将其设置为
true
。
- t1 也将在作业完成时提升 t2 的中断标志。
在 T2
通过将它放在 try/catch 块中来保护您的 readLine() 调用,捕获 IOException。如果存在循环,您可能还需要考虑自己检查中断标志,以防它在 readLine() 调用之间设置;您使用 Thread.interrupted()
执行此操作,其中 return 为 true 并在旗帜升起时降低旗帜。通常,类似于 while (!Thread.interrupted() && other conditions) { /* main loop here */ }
.
在 IOException 捕获处理程序中,检查 t1 的 'we are done' 标志(即 AtomicBoolean)。如果它显示 'we are done',则将 IOEx 解释为只是被通知工作已完成(因此,不要在任何地方记录它 - 你期望它发生)。但是,如果尚未设置 'we are done' 标志,则 IOException 表示输入管道存在实际 I/O 问题,这当然会发生。您应该照常进行(这通常意味着,将其向前抛出,以便应用程序崩溃并显示完整的日志,您无法理智地响应输入管道出现 I/O 问题,而不是退出并显示有关发生的事情的调试信息).所以,只需抛出 IOException。如果你不能,throw new UncheckedIOException(thatIoException);
就是你要找的。
注意事项
不幸的是,仅仅因为它可以在您的系统上运行并不意味着它可以在其他任何地方运行。正如我所说,在某些 VM impls System.in.read()
上是不可中断的,期间。除了极其激烈的步骤,您无能为力:停止成为命令行应用程序并显示 swing GUI window 或使其成为某种网络应用程序。
结束语
ready()
和 available()
几乎完全没用。它们并没有损坏,因为它们完全按照 javadoc 所说的这些方法做,但是如果你仔细阅读那个 javadoc,你会发现它们提供的是完全无用的.了解数据是否可用的唯一真正方法是实际尝试读取它,然后将您带入以下陷阱:嗯,在某些平台上,这是不可中断的。是的。糟透了。从 API 保证它可以在所有平台上运行的意义上说,没有可靠的解决方案可用。调用这些方法的所有代码中有 99.5% 都已损坏。您不太可能想调用这些方法。
看似单纯的话题,其实有点复杂。当您从标准输入读取数据时,通常只是调用操作系统。它不会 return 直到它实际输入到 return 并且不知道 Java 的中断机制。它被描述为副产品 .
您可以做的是提供您自己的 InputStream
而不是直接使用 System.in
,并以仅进入 System.in.read()
的方式实现其 read()
方法当 System.in.available()
这么说的时候。在那之前,只需延迟一些重复检查,比如使用 Thread.sleep()
无论如何都会被打断:
public static void main(String[] args) {
Thread main = Thread.currentThread();
// try (Scanner sc = new Scanner(System.in)) {
try (Scanner sc = new Scanner(new InputStream() {
@Override
public int read() throws IOException {
while (System.in.available() == 0)
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
throw new IOException();
}
return System.in.read();
}
})) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
main.interrupt();
}
}).start();
String line = sc.nextLine();
System.out.println(line);
System.out.println(main.isInterrupted());
} catch (Exception ex) {
System.out.println("Time's up, probably. Actual exception: " + ex);
System.out.println(main.isInterrupted());
}
}
如果您注释 try(Scanner...
-})) {
块并取消注释单行变体,您可以尝试它本身不起作用:您总是需要输入一些东西,只是System.out.println(main.isInterrupted());
的结果会告诉您是在 5 秒内完成还是花费了更多时间。
旁注:在您自己的尝试中,您中断了计时器线程本身,您需要引用另一个线程,在此示例中,这是 Thread main
变量。
我有以下主题:
Thread t1 = new Thread() {
@Override
public void run() {
while (!progress.equals(duration)) {
try {
Thread.sleep(1000);
progress = progress.plusSeconds(1);
// synchronized (this) { while (paused) { this.wait(); } }
} catch (InterruptedException e) {
interrupt();
}
}
}
};
t1.start();
我正在尝试实现一项允许用户使用控制台暂停和停止此线程的功能。基本上,这个:
Scanner sc = new Scanner(System.in);
int choice;
while (t1.isAlive()) {
System.out.println("Choose an option:\n1. Pause/Resume\n2. Stop");
choice = Integer.parseInt(sc.nextLine());
// if (choice == 1) { ... } else if (choice == 2) { t1.interrupt() }
// synchronized (t1) { t1.notify(); }
}
我的问题是,一旦 t1
结束,t1.isAlive()
的计算结果为 false,但程序不会退出 while
循环,因为它一直在等待来自用户。我想中断sc.nextLine()
,但我读到这是不可能的,因为线程被阻塞了。我该怎么做?
我尝试了以下方法:
Thread t2;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (t1.isAlive()) {
t2 = new Thread() {
@Override
public void run() {
try {
while (!br.ready())
Thread.sleep(200);
choice = Integer.parseInt(br.readLine());
} catch (InterruptedException e) {
} catch (IOException e) {
}
}
};
t2.start();
}
Supposedly, this should allow me to interrupt t2
,但我一定是做错了什么,因为它一直在打印 Chose an option: 1. Pause/Resume 2. Stop
,所以我无法检查它是否有效。
关键问题是 System.in 的 API 不能保证。 JVM 可以满足完整的 JVM 规范,即使它有一个 System.in
这样,如果它被中断,什么也不会发生 实际上完全不可能中断 System.in, 除了 System.exit
.
然而,幸运的是,大多数 JVM 实现并不是这样工作的:如果你在任何给定线程上设置中断标志,将会发生 3 件事:
任何指定要明确查看 em 的方法都将被中断:这些都是声明为
throws InterruptedException
的方法。如果线程的中断标志被提升,所有这些方法将立即停止等待,降低标志,并通过抛出InterruptedException
的方式return。是的,这意味着如果您首先升起中断标志(someThread.interrupt()
升起标志并且不做任何其他事情;是其他查看它的方法使魔法起作用),然后 调用例如Thread.sleep
,睡眠调用 returns 立即 (通过抛出 InterruptedEx)并且甚至不等待一毫秒。暂停线程但 不是 明确正确处理它的方法处于不确定状态:这取决于 java 运行时如果有任何事情发生。但是,通常 会发生一些事情。这些方法几乎总是抛出某种检查异常(对于数据库连接,
SQLEx
,对于网络、文件和管道操作,IOException
);当前正在等待发送或接收这些事物之一的数据的任何代码都将通过降低标志、中止操作和 returning 通过抛出已检查的异常和一条消息来处理升高的中断标志表示发生了中断。如果正在执行的代码根本不响应中断标志,那么什么也不会发生:标志保持上升状态,JVM 不会做任何其他事情;中断标志的要点是它刚刚被提升,然后你等待直到线程运行查看它的代码。希望这会很快发生,但不能保证。
这意味着您很可能需要做的就是:
在 T1
- 有某种 AtomicBoolean 对象,一旦作业完成,t1 就会将其设置为
true
。 - t1 也将在作业完成时提升 t2 的中断标志。
在 T2
通过将它放在 try/catch 块中来保护您的 readLine() 调用,捕获 IOException。如果存在循环,您可能还需要考虑自己检查中断标志,以防它在 readLine() 调用之间设置;您使用
Thread.interrupted()
执行此操作,其中 return 为 true 并在旗帜升起时降低旗帜。通常,类似于while (!Thread.interrupted() && other conditions) { /* main loop here */ }
.在 IOException 捕获处理程序中,检查 t1 的 'we are done' 标志(即 AtomicBoolean)。如果它显示 'we are done',则将 IOEx 解释为只是被通知工作已完成(因此,不要在任何地方记录它 - 你期望它发生)。但是,如果尚未设置 'we are done' 标志,则 IOException 表示输入管道存在实际 I/O 问题,这当然会发生。您应该照常进行(这通常意味着,将其向前抛出,以便应用程序崩溃并显示完整的日志,您无法理智地响应输入管道出现 I/O 问题,而不是退出并显示有关发生的事情的调试信息).所以,只需抛出 IOException。如果你不能,
throw new UncheckedIOException(thatIoException);
就是你要找的。
注意事项
不幸的是,仅仅因为它可以在您的系统上运行并不意味着它可以在其他任何地方运行。正如我所说,在某些 VM impls System.in.read()
上是不可中断的,期间。除了极其激烈的步骤,您无能为力:停止成为命令行应用程序并显示 swing GUI window 或使其成为某种网络应用程序。
结束语
ready()
和 available()
几乎完全没用。它们并没有损坏,因为它们完全按照 javadoc 所说的这些方法做,但是如果你仔细阅读那个 javadoc,你会发现它们提供的是完全无用的.了解数据是否可用的唯一真正方法是实际尝试读取它,然后将您带入以下陷阱:嗯,在某些平台上,这是不可中断的。是的。糟透了。从 API 保证它可以在所有平台上运行的意义上说,没有可靠的解决方案可用。调用这些方法的所有代码中有 99.5% 都已损坏。您不太可能想调用这些方法。
看似单纯的话题,其实有点复杂。当您从标准输入读取数据时,通常只是调用操作系统。它不会 return 直到它实际输入到 return 并且不知道 Java 的中断机制。它被描述为副产品
您可以做的是提供您自己的 InputStream
而不是直接使用 System.in
,并以仅进入 System.in.read()
的方式实现其 read()
方法当 System.in.available()
这么说的时候。在那之前,只需延迟一些重复检查,比如使用 Thread.sleep()
无论如何都会被打断:
public static void main(String[] args) {
Thread main = Thread.currentThread();
// try (Scanner sc = new Scanner(System.in)) {
try (Scanner sc = new Scanner(new InputStream() {
@Override
public int read() throws IOException {
while (System.in.available() == 0)
try {
Thread.sleep(100);
} catch (InterruptedException ie) {
throw new IOException();
}
return System.in.read();
}
})) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
main.interrupt();
}
}).start();
String line = sc.nextLine();
System.out.println(line);
System.out.println(main.isInterrupted());
} catch (Exception ex) {
System.out.println("Time's up, probably. Actual exception: " + ex);
System.out.println(main.isInterrupted());
}
}
如果您注释 try(Scanner...
-})) {
块并取消注释单行变体,您可以尝试它本身不起作用:您总是需要输入一些东西,只是System.out.println(main.isInterrupted());
的结果会告诉您是在 5 秒内完成还是花费了更多时间。
旁注:在您自己的尝试中,您中断了计时器线程本身,您需要引用另一个线程,在此示例中,这是 Thread main
变量。