【问题标题】:How do I break a loop in one Thread from another Thread?如何从另一个线程中断一个线程中的循环?
【发布时间】:2017-01-06 01:09:14
【问题描述】:

我是 Java 新手,我正在尝试使用一个线程来完成另一个线程中的循环,而不管循环的状态如何。

public static void main(String[] args) {
    // Either start the other thread here
        while(true){
            // Or here, not quite sure
            // Do stuff
        }
    }
}



public class Timer implements Runnable{

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while(true) {
            long current = System.currentTimeMillis();
            if(current - start == 10000){
                // How do I notify the loop in main to break?
                break;
            }
        }
    }
}

我要做的是在 10 秒后在 main 中结束循环,无论其循环的状态如何,因为循环包含从System.in 读取的Scanner,并且它需要在 10 秒后停止,无论从键盘读取的内容与否。我认为最好的解决方案是运行一个计时器线程来计算秒数,然后在 10 秒后以某种方式通知另一个线程中的循环中断,但是,我不知道如何实现这个......

【问题讨论】:

  • 为什么需要另一个线程来计算时间?
  • 我想我需要另一个线程,这样它就不会干扰在主循环中执行的步骤。
  • 您认为最好的解决方案是编写一个线程来执行您实际上并不希望它执行的操作,然后让其他线程介入并使其执行正确的操作?嗯,没有。最好的办法是编写线程代码来执行您真正希望它执行的操作,并且只执行您想要执行的操作。这样您就不必强迫它做正确的事情。
  • 用循环正确编码线程。这是错误的做法。
  • 好的,我设法解决了这个问题:我交换了循环和计数器,以便 Main 包含计时器(带有毫秒的循环),而另一个线程包含带有 Scanner 的循环。读取线()。当主循环达到 10 秒时,它会中断,并调用另一个线程停止:Thread.interrupt()。虽然我对 Thread.interrupt() 没有停止 Scanner.readline(); 感到惊讶,但调用 System.exit(0) 确实停止了它,因此程序总体上具有预期的功能。谢谢大家的帮助!

标签: java multithreading timer while-loop


【解决方案1】:

如果您的目标是在 10 秒后停止循环,则没有理由使用另一个线程。只需查看当地时间:

public class Main {
    public static void main(String[] args) {
        // Instructions

        long start = System.currentTimeMillis();
        do {
            //Instructions
            //Instructions
            //...
            //Instructions   
        } while (System.currentTimeMillis() - start < 10000);
    }
}

【讨论】:

  • 如果我这样做,无论循环状态如何,都会检查条件 System.currentTimeMillis() - start
  • @Andrew 每次重复前都会检查。
  • 好吧,我需要在 10 秒后完成循环,并且必须中断/跳过/忽略其中的一些操作。其中大多数都是简单的操作(创建一些对象并比较一些数字),但我也有一个 Scanner.readLine(); (我在互联网上阅读的扫描仪正在阻塞线程(?),或者它可能仅在从套接字读取时才阻塞)。无论如何,我希望在满足条件时跳过/忽略循环中的操作,而不是在重复指令之前。
  • @Andrew 那么这些答案都不起作用。您必须编写自定义代码来检查每一步的时间或标志。
  • 嗯,我想我必须在每一步之后检查。但是,会跳过 Scanner.readLine() 吗?我的意思是,我不想写一些东西并按 Enter 以进入下一步,即验证和中断。当条件满足时,我想以某种方式忽略 Scanner.readLine()。
【解决方案2】:

这样的事情怎么样:

public static void main(String[] args) {
    // Instructions
    AtomicBoolean shouldStop = new AtomicBoolean(false);

   Timer timer = new Timer(shouldStop);
   // start the timer thread
        while(true){
            if (shouldStop.get()) {
               break;
            }

            //Instructions
        }
    }
}



public class Timer implements Runnable{
   private final AtomicBoolean shouldStop;

   public Timer(AtomicBoolean shouldStop) {
        this.shouldStop = shouldStop;
   }

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while(true){
            long current = System.currentTimeMillis();
            if(current - start == 10000) {
                shouldStop.set(true);
                break;
            }
        }
    }
}

【讨论】:

  • 顺便说一句:另外两个答案是正确的,您不需要另一个线程并且睡眠会更有效。我的回答假设您真的在询问如何将信号从一个线程发送到另一个线程。但是,如果您真正想做的只是在 10 秒后停止,请听取其他答案之一的建议。
  • 是的,volatile 在这里很好。
  • 最好使用while (!shouldStop.get()) {
  • @ChaiT.Rex 同意。试图展示一个对他的初始代码进行最少编辑的解决方案。我认为这可能更清楚。
【解决方案3】:

不要重新发明轮子。使用 ExecutorServiceget() 超时:

ExecutorService executor = Executors.newSingleThreadExecutor();

// Here's a lambda, but you could use an instance of a normal Runnable class
Runnable runnable = () -> {
    while(true){
        // Do stuff
    }
};

Future<?> future = executor.submit(runnable);

try {
    future.get(10, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    // the task timed out
    future.cancel(true); // this will kill the running thread
} catch (InterruptedException | ExecutionException e) {
    // the runnable exploded
}

【讨论】:

  • 只是想知道,对于future.cancel(true),如果任务仍在运行,我相信它只会做一个中断。如果Runnable 是一个无限循环,没有意识到中断的逻辑,它不会被停止。我的理解正确吗?
【解决方案4】:

解决问题最安全的方法是使用 volatile 变量:

public class Main {

    private static volatile boolean keepRunning = false;

    public static void main(String[] args) {
        keepRunning = true;

        while(keepRunning) {
            //Instructions
            //...
            //Instructions   
        }
    }
}

public class Timer implements Runnable {

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while(true){
            long current = System.currentTimeMillis();
            if(current - start == 10000){
                // Notify the loop in Main to break
                keepRunning = false;
                break;
            }
        }
    }

}

【讨论】:

  • 这很接近我的需要,但是我如何使用 var keepRunning 来打破 main 中的循环,而不管它的状态如何?不是在循环中的所有指令都完成并准备重复时,而是在 keepRunning 为假时,这意味着我希望在 keepRunning 变为假时跳过/忽略循环中的指令。
  • (ppl,如果您投反对票,请解释原因)。对于你的问题,答案是:你不能。使用多线程没有“正确的时间”。如果您的某些指令正在阻塞调用,(从文件/套接字读取、睡眠等)您可以调用Thread.interrupt(),请参阅docs.oracle.com/javase/tutorial/essential/concurrency/…。但就纯代码而言,这仍然是一种协作机制。
  • 我不知道为什么如此简单且性能友好的解决方案被否决。 Java 程序员需要保持用 KISS 原则编写优雅代码的能力,而不是每次都使用 JDK 提供的海量框架。
【解决方案5】:

对此有很多解决方案。这是其中之一:

public class Main {

    public static void main(String... args) {
        Thread thread = () -> {
            while(true) {
                // We call Thread.interrupted to check the interrupted status of the Thread.
                // This method also clears the interrupted status of the Thread.
                if(Thread.interrupted()) {
                    break;
                }
                // code...
            }
        }

        Thread timer = () -> {
            long start = System.currentTimeMillis();
            while(true) {
                long current = System.currentTimeMillis();
                if(current - start == 10_000){ // underscore for clarity
                    // This causes the thread to interrupt. The next pass
                    // in our loop in "thread" will first check its interrupted status
                    // before continuing, and will break if the status is interrupted
                    thread.interrupt();
                    break;
                }
            }
        }

        // wrap in synchronized block to ensure both threads run simultaneously
        synchronized(Main.class) {
            thread.start();
            timer.start();
        }
    }
}

说明

在这个解决方案中,我们使用 interrupts 使 thread 中的 while 循环中断。中断会在它正在执行的任何点停止正常的线程操作,但前提是线程调用了抛出InterruptedException 的方法,并且线程在catch 块中返回(或中断)。 Thread.interrupt()interrupt status 设置为中断,但实际上并不中断线程的执行。 threadrun方法被中断后再次调用。由于我们在循环开始时检查了Thread.interrupted(),因此当进入循环时循环将中断,run 退出并且线程停止运行。使用Thread.interrupted(),您必须在同一个线程中检查其中断状态,然后选择是否清除状态。

附带说明,如果我们在循环中的唯一分支(if 语句)检查该线程的中断状态,那么如果我们简单地将 while 循环声明为 @,它可能更易于阅读且效率更高987654333@。否则应该保持原样。

其他解决方案

这里的一些其他答案说明了其他解决方案,尽管有些解决方案以不同的方式回答了您的整个问题。其他一些解决方案包括使用 Oliver Dain 回答的 AtomicBoolean;另一个使用全局 lock object 和 volatile 布尔值或 AtomicBoolean。

有很多解决方案,但在大多数用例中,中断解决方案似乎是最简单和最方便的。

【讨论】:

    猜你喜欢
    • 2019-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多