Является ли Java Regex Thread Safe?

104

У меня есть функция, которая использует Pattern#compileи Matcherдля поиска шаблона в списке строк.

Эта функция используется в нескольких потоках. Каждый поток будет иметь уникальный шаблон, передаваемый в Pattern#compileпри создании потока. Количество потоков и шаблонов является динамическим, что означает, что я могу добавить больше Patternпотоков и потоков во время настройки.

Нужно ли использовать synchronizeэту функцию, если она использует регулярное выражение? Является ли регулярное выражение в потоке Java безопасным?

jmq
источник

Ответы:

132

Да , из документации Java API для класса Pattern

Экземпляры этого (Pattern) класса неизменяемы и безопасны для использования несколькими параллельными потоками. Экземпляры класса Matcher небезопасны для такого использования.

Если вы изучаете код, ориентированный на производительность, попробуйте сбросить экземпляр Matcher с помощью метода reset () вместо создания новых экземпляров. Это приведет к сбросу состояния экземпляра Matcher, что сделает его пригодным для следующей операции регулярного выражения. Фактически, именно состояние, поддерживаемое в экземпляре Matcher, отвечает за его небезопасность для одновременного доступа.

Винит Рейнольдс
источник
17
Объекты шаблона являются потокобезопасными, но compile()метод может быть нет. За прошедшие годы было обнаружено две или три ошибки, которые приводили к сбою компиляции в многопоточных средах. Я бы рекомендовал делать компиляцию в синхронизированном блоке.
Алан Мур,
4
Да, в классе Pattern возникали ошибки параллелизма, и мы будем признательны за ваш совет о синхронизированном доступе. Однако первоначальные разработчики класса Pattern намеревались сделать класс Pattern потокобезопасным, и это контракт, на который может положиться любой программист Java. Откровенно говоря, я бы предпочел иметь локальные переменные потока и принять минимальное снижение производительности, чем полагаться на поточно-безопасное поведение по контракту (если я не видел код). Как говорится, «многопоточность - это просто, правильная синхронизация - сложная задача».
Vineet Reynolds,
1
Обратите внимание, что источник «Pattern» находится в дистрибутиве Oracle JDK (согласно oracle.com/technetwork/java/faq-141681.html#A14 : «Сам Java 2 SDK, Standard Edition содержит файл с именем src.zip, который содержит исходный код для общедоступных классов в пакете java »), так что можно быстро взглянуть на него.
Дэвид Тонхофер
@DavidTonhofer Я думаю, что наш последний JDK может иметь правильный код без ошибок, но поскольку промежуточные файлы .class Java могут интерпретироваться на любой платформе любой совместимой виртуальной машиной, вы не можете быть уверены, что эти исправления существуют в этой среде выполнения. Конечно, в большинстве случаев вы знаете, какая версия работает на сервере, но проверять каждую версию утомительно.
TWiStErRob
12

Поточная безопасность с регулярными выражениями в Java

РЕЗЮМЕ:

API регулярных выражений Java был разработан для того, чтобы один скомпилированный шаблон можно было использовать для нескольких операций сопоставления.

Вы можете безопасно вызывать Pattern.matcher () для одного и того же шаблона из разных потоков и безопасно использовать сопоставители одновременно. Pattern.matcher () безопасно создавать сопоставители без синхронизации. Несмотря на то, что метод не синхронизирован, он является внутренним для класса Pattern, изменчивая переменная, называемая compiled, всегда устанавливается после построения шаблона и считывается в начале вызова matcher (). Это заставляет любой поток, ссылающийся на Pattern, правильно "видеть" содержимое этого объекта.

С другой стороны, вы не должны использовать Matcher между разными потоками. Или, по крайней мере, если вы когда-либо использовали, вам следует использовать явную синхронизацию.

адатапост
источник
2
@akf, Кстати, вы должны заметить, что это дискуссионный сайт (как и этот). Я считаю все, что вы там найдете, не лучше или хуже, чем информация, которую вы найдете здесь (то есть, это не «Единственное истинное слово от Джеймса Гослинга»).
Боб Кросс
3

Хотя вам нужно помнить, что безопасность потоков также должна учитывать окружающий код, вам, похоже, повезло. Тот факт, что сопоставители создаются с использованием метода фабрики сопоставлений Pattern и не имеют общедоступных конструкторов, является положительным знаком. Точно так же вы используете статический метод compile для создания охватывающего Pattern .

Короче говоря, если вы сделаете что-то вроде примера:

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

у тебя все должно быть хорошо.

Продолжение примера кода для ясности: обратите внимание, что этот пример строго подразумевает, что созданный таким образом Matcher является локальным для потока с шаблоном и тестом. То есть вы не должны открывать созданный таким образом Matcher для каких-либо других потоков.

Честно говоря, это риск возникновения вопросов о безопасности потоков. Реальность такова, что любой код можно сделать небезопасным для потоков, если вы достаточно постараетесь. К счастью, есть замечательные книги, которые учат нас множеству способов испортить наш код. Если мы избегаем этих ошибок, мы значительно уменьшаем нашу собственную вероятность возникновения проблем с потоками.

Боб Кросс
источник
@Jason S: локальность потоков - это один из очень простых способов достижения безопасности потоков, даже если внутренний код не безопасен для потоков. Если бы только один метод мог когда-либо иметь доступ к определенному методу одновременно, вы обеспечили безопасность потоков извне.
Боб Кросс,
1
Хорошо, значит, вы просто говорите, что воссоздание шаблона из строки в точке использования лучше, чем сохранение его для повышения эффективности, рискуя столкнуться с проблемами параллелизма? Я дам вам это. Меня смутило предложение о фабричных методах и публичных конструкторах, которое кажется отвлекающим маневром в этой теме.
Джейсон С.
@Jason S, нет, фабричные методы и отсутствие конструкторов - вот некоторые из способов уменьшить угрозу связывания с другими потоками. Если единственный способ получить Matcher, который идет с моим Pattern, - это p.matcher (), никто другой не сможет повлиять на мой Matcher. Тем не менее, я все еще могу доставить себе неприятности: если у меня есть общедоступный метод, который возвращает этот Matcher, другой поток мог бы получить его и повлиять на него. Короче говоря, параллелизм - это сложно (на ЛЮБОМ языке).
Боб Кросс
2

Беглый взгляд на код Matcher.javaпоказывает набор переменных-членов, включая сопоставляемый текст, массивы для групп, несколько индексов для сохранения местоположения и несколько booleans для другого состояния. Все это указывает на состояние Matcher, которое не будет вести себя должным образом, если к нему будут обращаться несколько Threads. Так же как и JavaDoc :

Экземпляры этого класса небезопасны для использования несколькими параллельными потоками.

Это проблема только в том случае, если, как указывает @Bob Cross, вы изо всех сил разрешаете использовать свой Matcherв отдельных Threads. Если вам нужно это сделать и вы думаете, что синхронизация будет проблемой для вашего кода, у вас есть вариант - использовать ThreadLocalобъект хранилища для поддержки Matcherкаждого рабочего потока.

АКФ
источник
1

Подводя итог, вы можете повторно использовать (сохранять в статических переменных) скомпилированные шаблоны и сказать им, чтобы они предоставили вам новые сопоставители, когда это необходимо, для проверки этих шаблонов регулярных выражений на некоторой строке

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, использованного выше для проверки электронной почты ( в случае, если он не подходит для проверки электронной почты, как это размещено здесь)

Джордж Бирбилис
источник
3
Спасибо, что разместили свой ответ! Не забудьте внимательно прочитать FAQ по саморекламе . Кто-то может увидеть этот ответ и сообщение в блоге, на которое имеется ссылка, и подумать, что вы разместили сообщение в блоге только для того, чтобы вы могли ссылаться на него отсюда.
Эндрю Барбер,
2
Зачем заморачиваться static {}? Вы можете встроить инициализацию этой переменной, а также сделать Pattern final.
TWiStErRob
1
Я поддерживаю мнение TWiStErRob: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);лучше.
Кристоф Русси