当 Matcher.find() 运行时间过长时,如何终止它?

Posted

技术标签:

【中文标题】当 Matcher.find() 运行时间过长时,如何终止它?【英文标题】:How to terminate Matcher.find(), when its running too long? 【发布时间】:2011-10-30 20:14:01 【问题描述】:

想知道终止长时间运行的正则表达式匹配的技术(java matcher.find() 方法)。也许继承 Matcher 并添加一些逻辑以在 x 次迭代后终止?

基本上我是使用遗传算法生成正则表达式,所以我对它们没有太多控制权。然后我根据一些文本对每个文本进行测试,看看它们是否与文本的某个目标区域匹配。

因此,由于我是在随机生成这些正则表达式,因此会发生一些疯狂的事情,它会占用大量的 cpu,并且一些 find() 调用需要一段时间才能终止。我宁愿在一段时间后杀死他们,但不确定最好的方法。

所以如果有人有想法,请告诉我。

【问题讨论】:

【参考方案1】:

有一个解决方案here 可以解决您的问题。 (这个问题和你的问题一样。)

本质上,它是一个可以注意到线程中断的 CharSequence。

该答案的代码:

/**
 * CharSequence that noticed thread interrupts -- as might be necessary 
 * to recover from a loose regex on unexpected challenging input. 
 * 
 * @author gojomo
 */
public class InterruptibleCharSequence implements CharSequence 
    CharSequence inner;
    // public long counter = 0; 

    public InterruptibleCharSequence(CharSequence inner) 
        super();
        this.inner = inner;
    

    public char charAt(int index) 
        if (Thread.interrupted())  // clears flag if set
            throw new RuntimeException(new InterruptedException());
        
        // counter++;
        return inner.charAt(index);
    

    public int length() 
        return inner.length();
    

    public CharSequence subSequence(int start, int end) 
        return new InterruptibleCharSequence(inner.subSequence(start, end));
    

    @Override
    public String toString() 
        return inner.toString();
    

用这个包裹你的字符串,你可以中断线程。

【讨论】:

看起来很有希望。比我将要尝试的更容易(用我自己的版本替换 jdk Pattern 类,并使用 Xbootclasspath 来加载它而不是默认值)。 但是,其他答案中留下的一些 cmets 让我认为它可能并不总是有效。不过值得一试。它假设当我们陷入这类循环时会调用 charAt,但情况并非总是如此。【参考方案2】:

最坏的情况是:

您可以在另一个线程中运行正则表达式匹配,如果运行时间过长,您可以thread.stop() 它。

【讨论】:

对,我在一个单独的线程中运行。我很确定我之前尝试过 thread.stop() 并且它引起了一些奇怪的问题。当然,它已被弃用,我们被告知不要使用它等等。你有没有在最近的 jvm 上成功使用过 thread.stop()? 好吧,我已经尝试过了一段时间。我认为 JVM 刚刚死了,或者没有堆栈跟踪或任何东西。可能是由于其他原因,因为几乎没有留下任何证据。我总是可以再试一次。【参考方案3】:

只显示另一个解决方案。

您可以使用对输入不敏感且比Java标准库快数百倍的NFA算法。

我认为对输入的敏感性是导致您问题的最初原因。

你可以在这里查看介绍:Regular Expression Matching Can Be Simple And Fast (but is slow in Java, Perl, php, Python, Ruby, ...)

我在这里也回答了一个类似的问题,更详细:Cancelling a long running regex match?

【讨论】:

【参考方案4】:

一个可能的解决方案是在一个单独的线程中产生“匹配”,它有一个好处是它不会阻塞主线程。您可以创建一个自定义的Callable,它在持续时间/阈值到期后返回null,如果成功则返回“匹配”结果。

【讨论】:

这仅在匹配器响应Thread.intterupt() 时有效。 我已经在单独的线程中运行这些,但不确定这有什么帮助,因为除非我可以停止线程,否则它们会继续在后台运行。 @Sanjay,我不认为 interrupt() 会神奇地停止这些失控的线程,但也许我错了。 你没有错。 Thread.interrupt 仅与应用程序对中断的响应一样有用 你们当然是对的。我完全忘记了 Matcher 方法不响应 Thread.interrupt。作为最后的手段,您可以通过在每个循环中检查 Thread.interrupt 来尝试猴子修补“Matcher”类方法。 是的,我现在正在查看线程转储,它基本上是对 Pattern 类中所有这些匹配方法的调用堆栈。所以我看到的是 Pattern$GroupHead.match,然后是 Pattern$Ques.match,然后是 Pattern$Curly.match(。所以我可能需要做的是子类 Pattern,并且在每个匹配方法的开头,插入一些代码如果某个时间已过或可能调用 thread.wait(),让它们全部返回 false?【参考方案5】:

您需要使用另一个线程并在超时时停止它。

有两种停止方式:Thread#stop() 和 Thread#interrupt()。

使用 Thread.stop() 相当危险,而且 Matcher 不会响应 Thread.interrupt(响应中断是一种选择加入的行为)。

但是有一个非常聪明的解决方案,一些细节是here。使用提供的 InterruptibleCharSequence (它包装你的字符串并且几乎像一个一样工作,但它增加了对 Thread#interrupt() 的支持),然后构建你自己的 Callable 返回任何匹配器返回。现在可以使用 FutureTask / ThreadPool 组合来执行每个可运行对象,并且您可以使用任何所需的超时来获得结果:

Boolean result = myMatchingTask().get(2, TimeUnit.SECONDS)

如果您在 Java EE 环境中,您可以跳过复杂的部分,只需使用 InterruptipleCharSequence 和 @Asynchronous 调用即可。

如果这听起来很神秘,请询问详情。

【讨论】:

【参考方案6】:

如果我是你,我会创建自己的类,放在我的应用程序和你用来匹配的库之间,并实现像“中断”这样的方法,你需要杀死线程并管理匹配那样。

【讨论】:

以上是关于当 Matcher.find() 运行时间过长时,如何终止它?的主要内容,如果未能解决你的问题,请参考以下文章

matcher.find() 匹配成功,但是System.out.println(matcher.find())返回false

JAVA正则表达式,matcher.find和 matcher.matches的区别

正则表达式里matcher.find()一直为false

正则表达式Matcher.find报错 java.lang.StackOverflowError解决

当 JSON 调用时间过长时允许用户取消 MBProgressHUD

为什么我在java中一次Matcher.find()的执行中得到所有的匹配?[重复]