【问题标题】:How to set a time limit on a java function running a regex如何为运行正则表达式的 java 函数设置时间限制
【发布时间】:2009-08-15 20:56:47
【问题描述】:

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

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

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

所以,我在我的 class2 所在的同一个文件中创建了一个新的 RegexpThread 类。然后我将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 块”中,它正在检查“resultXml”字段是否为空字符串。如果它是一个空字符串,那么这意味着正则表达式在文档中没有找到它的字符串。但如果不是空字符串,则表示正则表达式已在文档中找到该字符串并将结果分配给“resultXml”字段。

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

【问题讨论】:

  • 尝试重新格式化以使其显示为代码,但您似乎有一些不匹配的大括号。不知道你打算怎么做,所以离开它

标签: java multithreading


【解决方案1】:

我在这里可能弄错了,但我认为终止线程have been deprecated for some time 的所有方法。推荐的方法是使用共享的isRunning 变量,您的工作线程会定期检查该变量并在设置后优雅地退出。

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

【讨论】:

    【解决方案2】:

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

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

    另一方面,问题的真正根源是(很可能)您以错误的方式使用正则表达式。要么你试图做一些对于单个正则表达式来说太复杂的事情,要么你的正则表达式不是最理想的。

    编辑:正则表达式耗时过长的典型原因是多个量词(?、、+)导致病态回溯。例如,如果您尝试使用正则表达式“^AAAAA 匹配由 N 个“A”字符后跟“B”的字符串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>
    

    请注意,没有关闭 &lt;/html&gt; 标记。这将在失败之前运行很长时间。 (我不完全确定复杂性是什么……但您可以根据自己的喜好通过实验估计它。)

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

    【讨论】:

      【解决方案3】:

      我暂时假设您的正则表达式代码是正确的,并且它确实是一些计算代码,是 CPU 绑定的 6 秒。

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

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

      不能这样做:

      1. 使用Thread.stop() 等。这已被弃用并且无法正常工作。
      2. 使用Thread.interrupt()。这会在线程上设置一个中断标志,该标志仅在线程执行 IO 时检查。如果线程受 CPU 限制,则该标志将从不被检查。

      鉴于上述情况,我将再次看看为什么正则表达式需要 6 秒才能匹配。正则表达式是否正确?您可以对较小的文本段执行正则表达式吗?

      【讨论】:

      • +1 这是迄今为止最好的建议:检查您的正则表达式并简化它。
      【解决方案4】:

      通过ExecutorService 启动你的线程并给它一个超时,像这样:

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

      awaitTermination() 将一直等到任务完成(以及此 ExecutorService 下的所有其他任务)、线程被中断或发生超时 - 以先到者为准。

      听起来这符合您的需求。

      【讨论】:

      • 再次阅读 JavaDoc for awaitTermination()。它阻塞了调用线程,而不是正在执行的线程。
      • 完全正确 - 启动 RegexThread -> 等待它完成,直到预定超时 -> 检查结果。
      • 如果在 awaitTermination() 完成时正则表达式线程仍在运行?
      【解决方案5】:

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

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

      您将遇到的真正问题是阻塞 IO - 例如,从网络读取。在这种情况下,您无法从 Java 中执行任何操作,因为该块实际上发生在 OS 内核中。

      【讨论】:

        【解决方案6】:

        Java Thread 类不具备处理这种中断的能力,因此不适合您的要求。

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

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

        【讨论】:

        【解决方案7】:

        我同意在使用正则表达式之前对其进行检查。 如果你需要一个安全网,你可以使用这样的东西......

        http://gist.github.com/630969

        【讨论】:

          【解决方案8】:

          下面的答案可能迟到了,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() 方法,将匹配器的输入重置为空字符串。参考code for Matcher class, Matcher reset(CharSequence input) method,它调用了Matcher reset(),它又将要匹配的文本区域的开始和结束设置为0,有效地停止了下一阶段匹配的匹配过程。该机制通过在设置的超时后终止匹配过程来为我工作。

          【讨论】:

            【解决方案9】:

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

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

            确保您在方法中的某处检查Thread#isInterrupted()

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

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

            【讨论】:

              【解决方案10】:

              你所做的对我来说看起来不错,这是我将如何修改它:

              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();
              

              【讨论】:

              • Thread.interrupt 并没有像你想象的那样做。除非处于等待状态,否则不会影响线程。
              猜你喜欢
              • 1970-01-01
              • 2017-01-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-23
              • 2022-12-11
              相关资源
              最近更新 更多