如何在运行正则表达式的java函数上设置时间限制

时间:2009-08-15 20:56:48

标签: java multithreading

我在java函数中运行正则表达式来解析文档,如果找到正则表达式指定的字符串则返回true,如果没有则返回false。但问题是,当文档不包含正则表达式指定的字符串时,返回false需要很长时间,如果执行时间超过6秒,我想终止该函数。

如何在该功能上设置6秒的时间限制,以便在超过6秒的时间内强制终止该功能。

我从类1调用类2的方法“方法1”。“方法1”调用同一类的“方法2”,即“类2”。方法2在文档上运行正则表达式代码。如果它找到正则表达式指定的字符串,则它将结果返回给方法1,方法1又将结果返回到“类1”中的方法,该方法称为类2的“方法1”。 现在的问题是,类2的method1和method2的执行时间不应超过6秒。

所以,我在同一个文件中创建了一个新的RegexpThread类,其中我的class2是。然后我将class2的method2移动到RegexpThread类中。然后每当调用方法1时,它都会按如下方式实例化RegexpThread类:

RegexpThread rt = new RegexpThread() {
  public void run() {
    method 2(m, urlCopy, document);
  }

};

rt.start();

try {
    rt.join(6 * 1000);
} catch (InterruptedException e) {
    return "y";
}

if(rt.getResultXml().equals("")) {
    return "g";
}

resultXml.append(rt.getResultXml());

return resultXml.toString();

显示的代码位于class2的方法1中。 RegexpThread类中的方法2对文档执行一些正则表达式搜索。 RegexpThread类中有一个名为“resultXml”的私有字段。如果方法2找到了正则表达式指定的字符串,则它将结果分配给私有字段“resultXml”。如果没有,则“resultXml”包含其默认值,即空字符串。

因此,在上面的“if block”中,它正在针对空字符串检查“resultXml”字段。如果它是一个空字符串,那么这意味着正则表达式没有在文档中找到它的字符串。但是如果它不是空字符串那么这意味着正则表达式在文档中找到了字符串并将结果分配给“resultXml”字段。

所以,看看这个并告诉我该怎么做......

10 个答案:

答案 0 :(得分:9)

我可能会在这里弄错,但我想所有终止线程have been deprecated for some time的方法。建议的方法是使用工作线程定期检查的共享isRunning变量,并在设置时正常退出。

这对你的情况不起作用,但它看起来像你正在治疗症状 - 而不是真正的问题。你应该发布你的regexp函数的代码,需要6秒才能执行。如果它是正则表达式本身,则执行时间可能是catastrophic backtracking的情况。

答案 1 :(得分:2)

我现在假设您的正则表达式代码是正确的,并且它确实是一些CPU限制为6s的计算代码。

鉴于上述情况,我认为你只有一个选择。在多个阶段/迭代中执行代码并检查变量以停止请求。您无法使用普通Pattern / Matcher代码执行此操作。

你可以通过以某种方式事先拆分你的输入字符串,然后一点一点地加入你的正则表达式来做到这一点(你的初始拆分必须独立于正则表达式)。

不能通过以下方式执行此操作:

  1. 使用Thread.stop()等。这已被弃用且无法正常使用。
  2. 使用Thread.interrupt()。这会在线程上设置一个中断标志,仅在线程执行IO时检查。如果线程受CPU限制,则该标志将永远不会被检查。
  3. 鉴于上述情况,我会再次考虑为什么正则表达式需要6s才能匹配。正则表达式是否正确?你可以在较小的文本段上执行regexp吗?

答案 2 :(得分:2)

有两种方法可以回答这个问题。

一方面,没有任何实用/有效的方法可以安全地杀死正在执行Matcher.find(...)Matcher.match(...)的线程。调用Thread.stop()会有效,但存在重大的安全问题。解决这个问题的唯一方法是开发自己的正则表达式引擎,定期检查interrupted标志。 (这并非完全不切实际。例如,如果GPL不是您的问题,您可以从OpenJDK中的现有正则表达式引擎开始。)

另一方面,问题的真正根源(最有可能)是你以错误的方式使用正则表达式。要么你想要为一个正则表达式做一些太复杂的事情,要么你的正则表达式不是最理想的。

编辑:正则表达式耗时过长的典型原因是多个量词(?,,+)导致病态回溯。例如,如果您尝试匹配一串N“A”字符后跟一个“B”与正则表达式“^ A A A A A A $“,计算的复杂性是(至少)O(N ** 5)。这是一个更“现实世界”的例子:

"(.*)<html>(.*)<head>(.*)</head>(.*)<body>(.*)</body>(.*)</html>(.*)"

现在想象如果你遇到这样的“网页”会发生什么:

<html><html><html><html><html><html><html><html><html><html>
<head><head><head><head><head><head><head><head><head><head>
</head></head></head></head></head></head></head></head></head></head>
<body><body><body><body><body><body><body><body><body><body><body>
</body></body></body></body></body></body></body></body></body></body>

请注意,没有结束</html>标记。这将在失败前运行很长时间。 (我不确定复杂性是什么......但是你可以通过实验估算出它的感觉。)

在这种情况下,一个简单的答案是使用更简单的正则表达式来定位6个标记元素,然后使用substring()之间提取内容。

答案 3 :(得分:0)

通过ExecutorService开始你的线程,并给它一个超时,如下:

ExecutorService pool = Executors.newFixedThreadPool(POOL_SIZE);
pool.execute(rt);
pool.awaitTermination(timeout, timeUnit);

awaitTermination()将等到任务完成(以及此ExecutorService下的所有其他任务),线程被中断或发生超时 - 这是首先出现的。

听起来这符合您的需求。

答案 4 :(得分:0)

你没有显示实际执行正则表达式的函数,所以我假设它从文件中读取行并在每行上执行正则表达式。

如果是这种情况,那么更好的解决方案是将超时值传递给该函数。在每N行(无论N可能是什么)之后,它会检查超时值。

您将遇到的真正问题是阻止IO - 例如,从网络读取。在这种情况下,你无法用Java做任何事情,因为这个块实际上是在OS内核中发生的。

答案 5 :(得分:0)

Java Thread类无法处理此类中断,因此不适合您的要求。

我将使用Process在单独的ProcessBuilder中实现该功能,并使用Process类提供的输入和输出流进行通信。强制中断由Process类的destroy方法提供。

我相信这是您所需要的正确,最安全的实施方案。不幸的是,Java并不容易以独立于平台的方式启动另一个Java进程,因此您必须将java可执行文件放到路径中并创建单独的main方法来执行此操作。这比应该的更难。

答案 6 :(得分:0)

我同意在使用前检查正则表达式。 如果您需要安全网,可以使用类似的东西......

http://gist.github.com/630969

答案 7 :(得分:0)

对于该帖子,以下答案可能太晚了,Java版本也已更改。但是,下面提到的机制对我有用。

中心思想是在进行匹配时将正在求值的输入文本更改为空字符串。以下测试的输入来自OWASP ReDoS example。输入的文本已更改,因为提供的文本长度不足以解决复杂性。

package org.test.xpath;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class InterruptableMatcherTest {

    public static void main(String[] args) throws Exception{

        Pattern pattern=Pattern.compile("^(([a-z])+.)+[A-Z]([a-z])+$");
        String input="aaaaaaaaaaaaaaaaaaaaadddddddddddddddddddddddddddddddddddddddaaaaaaaaaaaa!";

        PatternMatcher patternMatcher=new PatternMatcher(pattern, input);
        Thread thread=new Thread(patternMatcher);

        thread.start();

        Thread.sleep(1*1000);
        System.out.println("Done sleeping ...");
        if(patternMatcher.running)patternMatcher.reset();//Without this call the program will hang
        thread.join();

    }//main closing

}//class closing

class PatternMatcher implements Runnable{

    Pattern pattern;
    Matcher matcher;

    boolean running=false;

    PatternMatcher(Pattern pattern, String input) {

        this.pattern=pattern;
        matcher=this.pattern.matcher(input);

    }//constructor closing

    @Override
    public void run() {

        running=true;
        matcher.matches();
        running=false;

    }//run closing

    void reset(){

        System.out.println("Reset called ...");
        matcher.reset("");

    }//reset closing

}//class closing

reset()方法将匹配器的输入重置为空的String。请参阅code for Matcher class, Matcher reset(CharSequence input) method,它调用Matcher reset(),后者依次将要匹配的文本区域的开始和结尾设置为0,从而在下一阶段的匹配中有效地停止了匹配过程。该机制通过在设置超时后终止匹配过程为我工作。

答案 8 :(得分:-1)

您可以使用来自@Timeable的AOP和jcabi-aspects注释(我是开发人员):

@Timeable(limit = 1, unit = TimeUnit.SECONDS)
String yourMethod() {
  // execution as usual
}

确保您在方法的某个位置检查Thread#isInterrupted()

if (Thread.currentThread.isInterrupted()) {
  throw new IllegalStateException("time out");
}

当达到时间限制时,你的线程将isInterrupted()标志设置为true,你的工作就是正确处理这种情况并停止执行。

答案 9 :(得分:-2)

你所做的一切看起来很好,我在这里修改它:

final AtomicReference<String> resultXml = new AtomicReference<String>();

RegexpThread rt = new RegexpThread() {
  public void run() {
    method2(m, urlCopy, document, resultXml);
  }

};

rt.start();

try {
    rt.join(6 * 1000);
} catch (InterruptedException e) {
    return "y";
}

if(resultXml.get() == null) {
    rt.interupt();
    return "g";
}

resultXml.append(resultXml.get());

return resultXml.toString();