在下面的代码中,我使javascript在与主要线程不同的线程中运行。该脚本是一个无限循环,因此需要以某种方式终止。怎么样?
脚本开始运行后,调用.cancel()无效。但是,如果我在线程初始化之后立即调用.cancel(),它将终止它(注释行)。
package testscriptterminate; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.Timer; import java.util.TimerTask; public class TestScriptTerminate extends TimerTask{ private ExecutorService threads; private Future runScript; private Timer t; public TestScriptTerminate(){ t = new Timer(); t.schedule(this, 6000); //let the script run for a while before attempt to cancel threads = Executors.newFixedThreadPool(1); runScript = threads.submit(new ScriptExec()); //runScript.cancel(true); //will cancel here, before the script had a change to run, but useless, i want to cancel at any time on demand } @Override public void run(){ //after script has fully initialized and ran for a while - attempt to cancel. //DOESN'T WORK, thread still active System.out.println("Canceling now..."); runScript.cancel(true); } public static void main(String[] args) { new TestScriptTerminate(); } } class ScriptExec implements Runnable{ private ScriptEngine js; private ScriptEngineManager scriptManager; public ScriptExec(){ init(); } @Override public void run() { try { js.eval("while(true){}"); } catch (ScriptException ex) { System.out.println(ex.toString()); } } private void init(){ scriptManager = new ScriptEngineManager(); js = scriptManager.getEngineByName("nashorn"); } }
与Java一样,JavaScript(在Nashorn下)也不会像Java那样在紧密循环中响应中断。该脚本需要轮询中断并自动终止循环,或者可以调用一些检查中断并InterruptedException传播的内容。
InterruptedException
您可能会认为Nashorn在“只是运行脚本”,应该立即中断它。这并不适用,原因与在Java中不适用的原因相同:异步中断可能会损坏应用程序的数据结构,并且基本上没有办法避免它或从中恢复。
异步中断会带来与早已弃用的Thread.stop方法相同的问题。本文档对此进行了说明,它是注释中链接的文档的更新版本。
Thread.stop
Java线程原始弃用
另请参阅Goetz,《 Java并发实践》 ,第7章, 取消和关闭 。
检查中断的最简单方法是致电Thread.interrupted()。您可以从JavaScript很容易地调用它。这是对示例程序的重写,该示例程序在五秒钟后取消了正在运行的脚本:
Thread.interrupted()
public class TestScriptTerminate { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); void script() { ScriptEngineManager scriptManager = new ScriptEngineManager(); ScriptEngine js = scriptManager.getEngineByName("nashorn"); try { System.out.println("Script starting."); js.eval("while (true) { if (java.lang.Thread.interrupted()) break; }"); System.out.println("Script finished."); } catch (ScriptException ex) { ex.printStackTrace(); } } void init() throws Exception { Future<?> scriptTask = pool.submit(this::script); pool.schedule(() -> { System.out.println("Canceling now..."); scriptTask.cancel(true); }, 5, TimeUnit.SECONDS); pool.shutdown(); } public static void main(String[] args) throws Exception { new TestScriptTerminate().init(); } }
由于我们正在启动线程池,因此最好将其设置为预定的线程池,以便我们可以将其用于脚本任务和超时。这样,我们可以避免Timer和TimerTask,ScheduledExecutorService无论如何大多数情况下,它们都会被替换。
Timer
TimerTask
ScheduledExecutorService
处理和中断时通常的惯例是恢复中断位或进行InterruptedException传播。( 永远不要 忽略中断。)由于可以认为中断循环已经完成了对中断的处理,所以两者都没有必要,而且看起来只要让脚本正常退出就足够了。
这种重写还将大量工作从构造函数移到了init()方法中。这样可以防止实例从构造函数中泄漏到其他线程。在原始示例代码中,没有明显的危险- 实际上,几乎没有危险-但是,避免从构造函数中泄漏实例始终是一种好习惯。
init()