Java 正则表达式线程安全吗?

Posted

技术标签:

【中文标题】Java 正则表达式线程安全吗?【英文标题】:Is Java Regex Thread Safe? 【发布时间】:2010-11-24 12:16:24 【问题描述】:

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

这个函数在多个线程中使用。创建线程时,每个线程都会有一个独特的模式传递给Pattern#compile。线程和模式的数量是动态的,这意味着我可以在配置期间添加更多的Patterns 和线程。

如果它使用正则表达式,我是否需要在此函数上添加synchronize? java线程中的正则表达式安全吗?

【问题讨论】:

【参考方案1】:

,来自Pattern class 的 Java API 文档

这个(模式)类的实例是不可变的,并且可以安全地被多个并发线程使用。 Matcher 类的实例对于这种使用是不安全的。

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

【讨论】:

模式对象是线程安全的,但compile() 方法可能不是。多年来有两三个错误导致编译在多线程环境中失败。我建议在同步块中进行编译。 是的,Pattern 类中出现了并发错误,非常感谢您对同步访问的建议。然而,Pattern 类的最初开发者打算使 Pattern 类成为线程安全的,这是任何 Java 程序员都应该能够依赖的契约。坦率地说,我宁愿拥有线程局部变量并接受最小的性能损失,也不愿通过合同依赖线程安全行为(除非我看过代码)。正如他们所说的“线程很容易,正确的同步很难”。 请注意,“Pattern”的来源在 Oracle JDK 发行版中(根据oracle.com/technetwork/java/faq-141681.html#A14:“Java 2 SDK,标准版本身包含一个名为 src.zip 的文件,其中包含源代码用于 java 包中的公共类”),因此可以自己快速浏览一下。 @DavidTonhofer 我认为我们最新的 JDK 可能具有正确的无错误代码,但是由于 Java 的中间 .class 文件可以在任何平台上由任何兼容的 VM 解释,因此您无法确定这些修复存在于该运行时。当然,大多数时候您都知道服务器运行的是哪个版本,但是检查每个版本很繁琐。【参考方案2】:

Thread-safety with regular expressions in Java

总结:

Java 正则表达式 API 具有 被设计成允许单个 要共享的编译模式 多个匹配操作。

您可以放心地拨打电话 Pattern.matcher() 来自不同线程的相同模式和 安全地同时使用匹配器。 Pattern.matcher() 可以安全地构造匹配器,而无需 同步。虽然方法 不同步,在内部 模式类,一个 volatile 变量 调用编译后总是设置 构建一个模式并在 开始调用 ma​​tcher()。 这会强制任何线程引用 正确“看到”的模式 该对象的内容。

另一方面,你不应该分享 不同线程之间的匹配器。 或者至少,如果你曾经这样做过,你 应该使用显式同步。

【讨论】:

@akf,顺便说一句,你应该注意到这是一个讨论网站(很像这个)。我认为您在那里找到的任何信息都不会比您在此处找到的信息更好或更差(即,它不是詹姆斯·高斯林(James Gosling)的一个真实的话)。【参考方案3】:

虽然您需要记住线程安全也必须考虑到周围的代码,但您似乎很幸运。 Matchers 是使用 Pattern 的 matcher 工厂方法创建的并且缺少公共构造函数的事实是一个积极的信号。同样,您使用compile 静态方法来创建包含Pattern。

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

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

你应该做得很好。

为了清楚起见,跟进代码示例:请注意,此示例强烈暗示由此创建的 Matcher 对于 Pattern 和测试是线程本地的。即,您不应将由此创建的 Matcher 暴露给任何其他线程。

坦率地说,这是任何线程安全问题的风险。现实情况是,如果您足够努力,任何代码都可能成为线程不安全的。幸运的是,wonderfulbooks 教会了我们一大堆毁掉代码的方法。如果我们远离这些错误,我们就会大大降低自己出现线程问题的可能性。

【讨论】:

@Jason S:线程局部性是实现线程安全的一种非常直接的方法,即使内部代码不是线程安全的。如果一次只有一个方法可以访问特定方法,那么您已经在外部强制执行了线程安全。 好的,所以你只是说在使用点从字符串重新创建模式,比存储它要高效,冒着处理并发问题的风险?我会答应你的。我对关于工厂方法和公共构造函数的那句话感到困惑,这似乎是一个红鲱鱼 w/r/t 这个话题。 @Jason S,不,工厂方法和缺少构造函数是可以减少与其他线程耦合威胁的一些方法。如果您获得与我的 Pattern 配套的 Matcher 的唯一方法是通过 p.matcher(),那么没有其他人可以对我的 Matcher 产生副作用。但是,我仍然会给自己带来麻烦:如果我有一个返回该 Matcher 的公共方法,那么另一个线程可能会得到它并产生副作用。简而言之,并发是困难的(在任何语言中)。【参考方案4】:

快速查看Matcher.java 的代码会显示一堆成员变量,包括正在匹配的文本、组数组、一些用于维护位置的索引以及一些用于其他状态的booleans。这一切都指向一个有状态的Matcher,如果被多个Threads 访问,它将表现不佳。 JavaDoc也是如此:

这个类的实例对于多个并发使用是不安全的 线程。

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

【讨论】:

【参考方案5】:

总而言之,您可以重用(保留在静态变量中)已编译的模式,并告诉他们在需要时为您提供新的匹配器,以针对某些字符串验证这些正则表达式模式

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


请参阅http://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/(接近尾声)关于上面用于验证电子邮件的 RegEx 模式(以防它不适合在此处发布的电子邮件验证需求)

【讨论】:

感谢您发布您的答案!请务必仔细阅读FAQ on Self-Promotion。有人可能会看到此答案和链接到的博客文章,并认为您发布博客文章只是为了从此处链接到它。 为什么要打扰static ?您可以内联该变量初始化并创建 Pattern final 我赞同 TWiStErRob 的意见:private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN); 更好。

以上是关于Java 正则表达式线程安全吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何将 javascript 正则表达式转换为安全的 Java 正则表达式?

请问java的正则表达式和php的正则表达式通用吗

java正则表达式中[&&]用法

正则表达式模式灾难性回溯

正则表达式验证密码强度

正则表达式验证密码强度