【问题标题】:Scanner(System.in) - how to cancel/skip input waitingScanner(System.in) - 如何取消/跳过输入等待
【发布时间】:2013-04-04 04:56:11
【问题描述】:

我只是想知道如何在单独的线程中控制控制台输入?
我有线程 A 和线程 B 和线程 C; B 和 C 他们都控制用户输入...问题是我不太确定如何在 B 和 C 线程之间切换 scanIn.nextLine(); 因为 B 似乎在线程 C 可以中断 B 之前循环了两次不必要的迭代 :(

主线程:

  public class Main
        {
            private volatile ThreadGroup threadGroup=new ThreadGroup();//contains concurrent hash map...
            private volatile TaskManager taskManager=new TaskManager(threadGroup);
            private A a=new A(threadGroup);
            private B b=new B(threadGroup,taskManager);
            private C c=new C(threadGroup);


     Main()
    {

      b.start();

      threadGroup.add(a,"A");
      threadGroup.add(b,"B");
      threadGroup.add(c,"C");
    }

    public static void main(String args[]){new Main();}

        }

TaskManager 方法 sn -p:

...
public synchronized void threadCMaybeCanBeStartedLater()
{
      this.getThreadGroup().get("A").start(); 
}
...

thread 类似的代码(被覆盖的run方法调用):

public void loopIt()
{
   Random generator = new Random(); 
   A: while(!this.interrupted())
{
   Thread.sleep(1000);

   int i=generator.nextInt(100)+1;
   int j=generator.nextInt(100)+1;
   if(i==j){this.invokeC(); System.out.println("event : i==j");}

    }
 }

private void invokeC()
{
  if(!this.getThreadGroup().get("C").isAlive())this.getThreadGroup().get("C").start(); 
}

线程 B 代码如:

public void loopIt() throws InterruptedException
    {


        Scanner scanIn = new Scanner(System.in);

        B: while(!this.isInterrupted())
        {

            Thread.sleep(1000);

            String command= scanIn.nextLine();
...

         if(command.equals("a"))
        {   
            System.out.println("a was entered");
            this.getTaskManager().threadCMaybeCanBeStartedLater();//             
            continue;
        }
        if(command.equals("b"))
        {   
           System.out.println("b was entered");            
           continue;
        }
        if(command.equals("c"))
        {
            System.out.println("c was entered");
            continue;
        }
        else{System.out.println("no such command");}

    }

}

线程C(run方法调用)

public void loopIt() throws InterruptedException
        {
            getThreadGroup().get("B").interrupt();

            Scanner scanIn = new Scanner(System.in);

            C: while(!this.isInterrupted())
            {

                Thread.sleep(1000);

                String command= scanIn.nextLine();
    ...

             if(command.equals("d"))
            {   
                System.out.println("d was entered");             
                continue;
            }
            if(command.equals("e"))
            {   
               System.out.println("e was entered");            
               this.interrupt();
               break C;
            }
            if(command.equals("f"))
            {
                System.out.println("f was entered");
                continue;
            }
            else{System.out.println("no such command");}

        }

       getThreadGroup().get("B").start();

    }

...如您所见,主要代码概念(请参阅 A 线程 sn-p)是“您不知道 何时 线程 C 可以启动,但它何时启动你需要给它控制台";就这样;如果是 GUI 则没有问题,但类似控制台的应用程序会造成很大问题...

所以问题是……在这种情况下,如何立即从线程 C 中断/重新启动线程 B?

谢谢

【问题讨论】:

  • 我想你发了两次A。
  • 我不知道这段代码有什么意义。
  • 哦,天哪……这很明显;该代码正在测试一些传入事件“注入”的能力(在这种情况下,线程 A 扮演角色);如果线程 A 中发生了某些事情,那么它会从线程 B 为线程 C 获取控制台;主要问题是无法注入事件,因为 Scanner 永远阻塞或其他东西:( 我实际上也怀疑 System.in 静态性质,但我不确定......所以也许我不应该使用 Scanner 并且有一个alt 控制台输入解决方案,或者可能有更优化的方式来控制用户输入...
  • 您可以从这里尝试解决方案:stackoverflow.com/questions/19130822/… 我已经修复(解决)中断System.in 读取操作无法被中断的问题,除非用户按下回车键。

标签: java multithreading io


【解决方案1】:

使用线程类同步线程

  1. Thread.interrupt() 本身不会同步两个线程之间的逻辑和计时。

    Thread.interrupt() 表示调用者会喜欢线程在不久的将来一次中断。 interrupt() 方法设置一个中断标志。 isInterrupted() 方法检查是否设置了该标志(& 也再次清除该标志)。 Thread.sleep()、Thread.join()、Object.wait() 方法和一些 I/O 方法也会在抛出 InterruptedException 时检查并清除此标志。

    线程不会立即暂停,而是继续运行代码。内部线程逻辑由开发人员设计和实现:继续运行被认为是原子/紧急的线程代码,直到它到达“可中断点”,然后检查中断标志/捕获 InterruptedException 然后进行干净的暂停 - 通常通过 Thread.sleep()、Thread.join() 或 Object.wait(),有时通过退出 Thread.run() 完全因此永久停止线程。

    虽然所有这一切都在发生,但调用线程仍在运行,并将在中断生效之前执行不确定数量的代码......因此缺乏同步。一个线程中的代码与另一线程中的代码之间缺乏保证的happens-before 条件。

  2. 确实在两个线程之间同步逻辑和时序的一些方法(创建发生前条件):

    • thread1 调用 Thread2.join()

    • thread1 调用 SomeObject.wait(),thread2 调用 SomeObject.notify()

    • 在方法或块上同步

快速查看您的代码:

  1. 线程 B 在无限循环中运行 - 没有任何线程调用中断它,也没有调用它的线程等待()。但是,它会暂时阻塞,直到 System.in 有更多输入,然后继续。
  2. 线程 A 只会中断自己 - 如果您不调用 this.interrupt() 和 while(!this.isInterrupted()),则更易于分析逻辑:只需将 while 循环更改为:do { .... } while (i != j)
  3. 线程 A 只会中断自己 - 如果您不调用 this.interrupt() 和 while(!this.isInterrupted()),则更清晰且更容易分析逻辑:只需将 while 循环更改为:do { .... } while (!"e".equals(command))
  4. 线程 C 必须在其 while 循环的顶部进行以下调用:

     threadB.interrupt();
     synchronized(this) {
         try {
             this.wait();
         } catch (InterruptedException ie) {
         }
    
  5. 线程 B 必须在代码的最后一行进行以下调用:

     synchronized(threadC) {
             threadC.notify();
     }
    
  6. 从 I/O (nextLine()) 读取是一个阻塞和可中断的操作。在它旁边你介绍了Thread.sleep(),这也是一个阻塞和可中断的操作,它会在你的代码中引入人为的延迟——这不是必需的;删除。

  7. 您调用的唯一扫描程序方法是nextLine()。您将其用作 InputStreamReader 并且不进行任何扫描。此外,您没有缓冲输入。如果代码保持这样,请将“Scanner scanIn = Scanner(System.in)”替换为:“BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))”。
  8. 您调用的唯一ThreadGroup 方法是add() 和get()。你使用它就像HashMap 并且没有做任何线程组管理。如果代码保持这样,您可以将“ThreadGroup”替换为“HashMap”。然而,即使是 HashMap 似乎也太过分了——可以简单地使用构造函数/设置器将线程引用传递给其他线程,并完全避免使用 HashMap。
  9. 避免在循环内部过度使用continue - 尽量避免。最好通过使用 '} else if {' 将连续的 'if' 语句链接在一起来做到这一点......
  10. 主线程和线程 B 之间的潜在竞争条件。当线程 B 启动时(来自 Main()),它可能会在主线程执行更多代码之前执行多行代码 - B 可能会在主线程之前调用 ThreadGroup.get()线程调用了 ThreadGroup.add() x 3。解决方法:在 Main() 中,将 b.start() 放在 ThreadGroup.add() x 3 之后
  11. 一般而言,"a".equals(command) 比 command.equals("a") 更好 - 它处理空值,在没有 NPE 的情况下给出正确的结果(您在这里看起来很幸运 - 可能不会有空值)。

建议的更改:

public class ThreadA extends Thread {

    ThreadC threadC;

    public void setThreadC(ThreadC threadC) {
        this.threadC = threadC;
    }

    @Override
    public void run() {
        this.loopIt();
    }

    public void loopIt() {
        Random generator = new Random(); 
        int i, j;
        do {
            try { 
                Thread.sleep(1000);
            } catch (InterruptedException ie) {                
            }
            i=generator.nextInt(100)+1;
            j=generator.nextInt(100)+1;
        } while (i != j);
        threadC.start();
    }

}
public class ThreadB extends Thread {

    ThreadA threadA;
    ThreadC threadC;

    public void setThreadA(ThreadA threadA) {
        this.threadA = threadA;
    }
    public void setThreadC(ThreadC threadC) {
        this.threadC = threadC;
    }

    @Override
    public void run() {
        this.loopIt();
    }

    public void loopIt() {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String command = null;
        // loop until interrupted
        try {
            while (!this.isInterrupted()) {
                command = reader.readLine();
                if ("a".equals(command)) {   
                    System.out.println("a was entered");
                    if (threadA.getState() == Thread.State.NEW) {
                        threadA.start();
                    }
                } else if ("b".equals(command)) {   
                    System.out.println("b was entered");            
                } else if ("c".equals(command)) {
                    System.out.println("c was entered");
                } else if ("z".equals(command)) {
                    System.out.println("z was entered");
                    throw new InterruptedException("Command z interruption");
                } else {
                    System.out.println("no such command");
                }
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        } catch (InterruptedException ie) {
        }
        // Now notify ThreadC - it will wait() until this code is run
        synchronized(threadC) {
            threadC.notify();
        }
    }
}

public class ThreadC extends Thread {

    ThreadB threadB;

    public void setThreadB(ThreadB threadB) {
        this.threadB = threadB;
    }

    @Override
    public void run() {
            this.loopIt();
   }

    public void loopIt() {
        // Block until the lock can be obtained
        // We want thread B to run first, so the lock should be passed into Thread C constructor in an already locked state
        threadB.interrupt();
        synchronized(this) {
            try {
                // Put this thread to sleep until threadB calls threadC.notify().
                //
                // Note: could replace this line with threadB.join() - and remove  
                // from threadB the call to threadC.notify()
                this.wait();
            } catch (InterruptedException ie) {
            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
            String command = null;
            while (!"e".equals(command)) {
                try {
                    command= reader.readLine();
                    if ("d".equals(command)) {   
                        System.out.println("d was entered");             
                    } else if ("e".equals(command)) {    
                        System.out.println("e was entered");            
                    } else if ("f".equals(command)) {
                        System.out.println("f was entered");
                    } else if ("z".equals("command")) {
                        System.out.println("z was entered");
                    } else { 
                        System.out.println("no such command");
                    };
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }        
    }
}

【讨论】:

  • 这很有趣。谢谢你。您建议更改架构,但我没有看到线程 A 假定的修改;我的意思是“线程 A 应该如何根据新建议的修改以更优化的方式启动线程 C(invokeC() 方法)”或者您的意思是线程 A 代码应该保持不变?如您所见,基本概念是线程 B 获取命令“a”以打开“线程 C 随机启动计时器”(线程 A 充当随机启动器)所以这是最有趣的一点;所以请您提供更多详细信息
  • ThreadA 很有趣。我只是想知道您如何建议将所有部分与主线程放在一起?
  • 查看我的评论 cmets。不幸的是,我无法为您编写每一行代码。 :^)
  • 我明白 :) 我只是不明白 ThreadA 和 ThreadC(参见 ThreadB sn-p)在主线程中是否易失?而且我在想...您建议在 i==j 时停止 ThreadA 但是如果“我不想在 i==j 时停止它?并使它不停止自己但总是循环并以某种方式对 i== 做出反应怎么办? j?”我的意思是让 ThreadA 成为某种无休止的“事件监听器”?该事件发生在我的测试用例“when i==j”中......我已经编辑了 ThreadA sn-p 以清除概念
【解决方案2】:

nextLine() 不响应中断。你想做类似的事情

String command;
if (scanIn.hasNextLine())
    command = scanIn.nextLine();
else
    Thread.sleep(1000);

【讨论】:

  • 我知道;我的意思是如何立即从线程 B 中断/重新启动线程 A?
  • @user592704 那么我不确定你在问什么。你是说你无法精确控制 B 打断 A 的电话何时被注意到?这就是并发的本质。如果需要同步,则需要使用锁或 volatile 或其他一些构造。
  • 不,我可以控制何时中断,但中断不是立即发生的 :( ...当 B 调用类似 mythreads.getA().interrupt() 时确定:1) A '也许仍然'睡眠 2) A 正在等待输入'所以我输入' 3) 然后 A 被中断,B 使用控制台:S 我希望线程 A 工作,直到我需要线程 B 使用控制台。所以你说我必须同步一些点?请提供更多详细信息
  • 你试过我的答案了吗?听起来这就是您正在寻找的东西。在您的示例中,A 在获得interrupt() 时正在等待输入。因为扫描仪不会观察中断,所以它会永远阻塞。当您按下enter 时,控制会跳转到您的while 条件并且它会注意到中断。正如我所发布的,对此的解决方案是不阻止等待输入。试试我的解决方案,然后解释它为什么不能满足您的需求。
  • 是的,你是对的。似乎扫描仪永远阻塞了,所以很难有两个 scanIn.nextLine();一次调用,这就是为什么我问如何手动关闭“取消”它或其他东西:)
【解决方案3】:

您可以使用标志变量(作为全局变量)来控制每个线程中的while loop...

假设线程 A 有一个像这样的无限循环

while(true)
 while(x == 1){
   your code ...
 }
  Thread.sleep(2000);
}

当线程 b 启动时,您可以将 x 更改为 0(假设 x 是一个全局变量),然后当线程 b 完成执行时,在线程 b 代码的末尾将 x 更改为 1...

或者你可以根据标志值x从线程本身中断线程

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-10-12
    • 1970-01-01
    • 2021-01-19
    • 2014-06-20
    • 1970-01-01
    • 2013-01-13
    • 2011-12-17
    相关资源
    最近更新 更多