Java Regex线程安全吗?


104

我有一个函数,该函数使用Pattern#compileMatcher来在字符串列表中搜索模式。

此函数用于多个线程。每个线程将在Pattern#compile创建线程时传递给的唯一模式。线程和模式的数量是动态的,这意味着我可以Pattern在配置期间添加更多s和线程。

synchronize如果使用正则表达式,是否需要在此函数上放一个?Java线程中的正则表达式安全吗?

Answers:


132

是的,来自Pattern类的Java API文档

此类(模式)的实例是不可变的,可以安全地由多个并发线程使用。Matcher类的实例不安全用于此类用途。

如果您正在查看以性能为中心的代码,请尝试使用reset()方法重置Matcher实例,而不是创建新实例。这将重置Matcher实例的状态,使其可用于下一个正则表达式操作。实际上,正是Matcher实例中维护的状态才使它对于并发访问不安全。


17
模式对象是线程安全的,但compile()方法可能不是。这些年来,已经有两到三个错误导致编译在多线程环境中失败。我建议在同步块中进行编译。
艾伦·摩尔

4
是的,Pattern类中出现了并发性错误,对于同步访问的建议,我们深表感谢。但是,Pattern类的原始开发人员打算使Pattern类作为线程安全,并且这是任何Java程序员都应该能够依赖的契约。坦率地说,我宁愿拥有线程局部变量并接受对性能的最低要求,而不是依靠合同来依赖线程安全行为(除非我看过代码)。正如他们所说的“线程很容易,而正确的同步却很难”。
Vineet Reynolds

1
请注意,“模式”的来源位于Oracle JDK发行版中(根据oracle.com/technetwork/java/faq-141681.html#A14:“ Java 2 SDK,标准版本身包含一个名为src.zip的文件,该文件包含java包中公共类的源代码”),因此您可以快速浏览一下。
David Tonhofer 2013年

@DavidTonhofer我认为我们最新的JDK可能具有正确的无错误代码,但是由于Java的中间.class文件可以在任何平台上通过任何兼容的VM进行解释,因此您无法确定这些修补程序是否在该运行时中存在。当然,大多数时候您都知道服务器正在运行哪个版本,但是检查每个版本都很繁琐。
TWiStErRob '16

12

Java中带有正则表达式的线程安全

摘要:

Java正则表达式API已设计为允许在多个匹配操作之间共享单个编译模式。

您可以从不同的线程以相同的模式安全地调用 Pattern.matcher(),并安全地同时使用匹配器。 Pattern.matcher()可安全地构建不同步的匹配器。尽管该方法不是同步的,但在Pattern类内部,但始终在构造模式后设置一个称为compile的易失变量,并在对matcher()的调用开始时读取该变量 这会强制所有引用Pattern的线程正确地“查看”该对象的内容。

另一方面,您不应该在不同线程之间共享Matcher。或者至少,如果曾经这样做,则应使用显式同步。


2
@akf,顺便说一句,您应该注意,这是一个讨论站点(非常类似于此站点)。我认为您在这里找到的任何信息都不会比在这里找到的信息更好或更糟(即,这不是James Gosling的“一个真实的词”)。
鲍勃·克罗斯

3

尽管您需要记住线程安全性也必须考虑周围的代码,但是您似乎很幸运。这样的事实匹配器使用模式的创建匹配的工厂方法,缺乏公共的构造函数是一个积极的迹象。同样,您可以使用compile static方法创建包含的Pattern

简而言之,如果您执行类似示例的操作:

Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();

你应该做得不错。

为了清楚起见,对代码示例进行了后续操作:请注意,此示例强烈暗示由此创建的Matcher在Pattern和测试中是线程局部的。即,您不应将由此创建的Matcher暴露给任何其他线程。

坦白说,这就是任何线程安全问题的风险。现实情况是,如果您努力尝试,任何代码都可能被设置为线程不安全的。幸运的是,有很多很棒的 ,教给我们很多破坏代码的方式。如果我们避免这些错误,那么将大大降低我们自己出现线程问题的可能性。


@Jason S:即使内部代码不是线程安全的,线程局部性也是实现线程安全的一种非常简单的方法。如果一次只能使用一种方法可以访问特定方法,则可以从外部强制执行线程安全。
鲍勃·克罗斯

1
好的,所以您只是说在使用时从字符串重新创建模式比将其高效存储要好,这有处理并发问题的风险?我会给你的。我对关于工厂方法和公共构造函数的句子感到困惑,这似乎是一个无聊的话题。
杰森S

@Jason S,不,工厂方法和缺少构造函数是可以减少与其他线程耦合的威胁的一些方法。如果获取与我的模式匹配的Matcher的唯一方法是通过p.matcher(),则没有其他人可以对我的Matcher产生副作用。但是,我仍然会给自己造成麻烦:如果我有一个返回该Matcher的公共方法,则另一个线程可能会遇到该问题并产生副作用。简而言之,并发很难(使用任何语言)。
Bob Cross

2

快速浏览一下的代码,将看到Matcher.java一堆成员变量,包括要匹配的文本,组的数组,用于维护位置的一些索引以及boolean用于其他状态的s。所有这些都指向一个有状态的状态Matcher,如果被多个访问,状态将不会很好Threads。如此做的JavaDoc

此类的实例不适用于多个并发线程。

正如@Bob Cross指出的那样,这只是一个问题,如果您竭尽所能允许Matcher单独使用Threads。如果您需要执行此操作,并且认为同步将成为代码问题,那么您可以选择使用ThreadLocal存储对象来维护Matcher每个工作线程。


1

综上所述,您可以重用(保留静态变量)已编译的Pattern,并在需要验证它们针对某些字符串的正则表达式模式时,告诉它们为您提供新的Matchers。

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

/**
 * Validation helpers
 */
public final class Validators {

private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$";

private static Pattern email_pattern;

  static {
    email_pattern = Pattern.compile(EMAIL_PATTERN);
  }

  /**
   * Check if e-mail is valid
   */
  public static boolean isValidEmail(String email) { 
    Matcher matcher = email_pattern.matcher(email);
    return matcher.matches();
  }

}

有关上面用于验证电子邮件的RegEx模式,请参见http://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/(接近尾声)(万一它不适合那些需要通过电子邮件验证的人,因为它在这里发布)


3
感谢您发布答案!请务必仔细阅读有关自我促销常见问题解答。有人可能会看到此答案和链接到博客的文章,并认为您只是发布了博客文章,因此可以从此处链接到它。
安德鲁·巴伯

2
为什么要打扰static {}?您可以内联该变量初始化并进行初始化Pattern final
TWiStErRob

1
我赞成TWiStErRob的观点:private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);更好。
Christophe Roussy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.