Чи безпечна нитка Java Regex?


104

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

Ця функція використовується в декількох потоках. Кожна нитка матиме унікальний зразок, переданий до того, Pattern#compileколи створюється нитка. Кількість потоків і шаблонів є динамічними, це означає, що я можу додати більше Patterns і потоків під час конфігурації.

Чи потрібно мені ставити synchronizeцю функцію, якщо вона використовує регулярний вираз? Чи безпечний регулярний вираз в нитці Java?

Відповіді:


132

Так , з документації Java API для класу Pattern

Екземпляри цього класу (Pattern) є незмінними і безпечні для використання у кількох одночасних потоках. Примірники класу Matcher не безпечні для такого використання.

Якщо ви переглядаєте код, орієнтований на ефективність, спробуйте скинути екземпляр Matcher за допомогою методу reset (), а не створювати нові екземпляри. Це скине стан екземпляра Matcher, зробивши його корисним для наступної операції регулярного виведення. Фактично, держава, яка підтримується в екземплярі Matcher, відповідає за те, що вона є небезпечною для одночасного доступу.


17
Об'єкти шаблону є безпечними для потоків, але compile()метод може бути не таким. Протягом багатьох років було дві або три помилки, які спричинили збій компіляції у багатопотокових середовищах. Я рекомендую робити компіляцію в синхронізованому блоці.
Алан Мур

4
Так, у класі Шаблон було піднято помилки одночасності, і ваша порада щодо синхронізованого доступу оцінена. Однак оригінальні розробники класу Pattern мали намір зробити клас Pattern максимально безпечним для потоків, і це договір, на який повинен покладатися будь-який програміст Java. Якщо чесно кажучи, я б краще мати локальні змінні та приймати мінімальний показник продуктивності, ніж покладатися на безпечну поведінку потоку за контрактом (якщо я не бачив код). Як кажуть: "Нитка легко, правильно виправити синхронізацію".
Vineet Reynolds

1
Зауважте, що джерело " Шаблону " знаходиться в дистрибутиві Oracle JDK (За даними oracle.com/technetwork/java/faq-141681.html#A14 : "Java 2 SDK, стандартне видання містить файл, який називається src.zip, який містить вихідний код для публічних класів у пакеті java "), щоб можна було швидко заглянути.
Девід Тонхофер

@DavidTonhofer Я думаю, що наш останній JDK може мати правильний код без помилок, але оскільки Java-проміжні файли .class можуть бути інтерпретовані на будь-якій платформі будь-яким сумісним VM, ви не можете бути впевнені, що ці виправлення існують у той час виконання. Звичайно, більшу частину часу ви знаєте, в якій версії працює сервер, але перевіряти кожну версію доленочно.
TWiStErRob

12

Захист ниток із регулярними виразами на Java

РЕЗЮМЕ:

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

Ви можете сміливо викликати Pattern.matcher () за одним і тим же шаблоном з різних потоків і безпечно використовувати відповідники одночасно. Pattern.matcher () безпечно створювати відповідники без синхронізації. Хоча метод не синхронізований, внутрішній для класу Pattern, летуча змінна, що називається компільована, завжди встановлюється після побудови шаблону і зчитується на початку виклику до matcher (). Це змушує будь-який потік, що посилається на Шаблон, правильно "бачити" вміст цього об'єкта.

З іншого боку, вам не слід ділитися Matcher між різними потоками. Або принаймні, якщо ви коли-небудь це робили, ви повинні використовувати явну синхронізацію.


2
@akf, BTW, ви повинні зауважити, що це дискусійний сайт (подібний до цього). Я вважаю, що все, що ви знайдете там, не є кращим чи гіршим, ніж інформація, яку ви знайдете тут (тобто це не Єдине справжнє слово від Джеймса Гослінга).
Боб Крос

3

Хоча вам потрібно пам’ятати, що безпека потоку повинна враховувати також навколишній код, вам здається, що вам пощастило. Той факт , що Matchers створюються з використанням патерну узгодження фабричного методу і відсутністю державних конструкторів є позитивним знаком. Аналогічно, ви використовуєте статичний метод компіляції для створення охоплюючого шаблону .

Отже, коротше, якщо ви робите щось на зразок прикладу:

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

ви повинні робити добре.

Слідування за прикладом коду для наочності: зауважте, що цей приклад рівно передбачає, що створений таким чином Matcher є локальним потоком із шаблоном та тестом. Тобто, ви не повинні піддавати створений таким чином Матчер жодним іншим потокам.

Відверто кажучи, це ризик будь-якого питання щодо безпеки потоку. Реальність полягає в тому, що будь-який код можна зробити небезпечним для потоків, якщо ви постараєтесь досить. На щастя, є чудові книги, які навчають нас цілу купу способів, які ми могли б зіпсувати наш код. Якщо ми тримаємось осторонь цих помилок, ми значно зменшуємо власну ймовірність виникнення проблем з нарізкою.


@Jason S: Місцевість потоків - це дуже простий спосіб досягнення безпеки потоку, навіть якщо внутрішній код не є безпечним для потоку. Якщо лише один метод може отримати доступ до певного методу одночасно, ви застосували безпеку потоку зовні.
Боб Крос

1
добре, значить, ви просто говорите, що повторне створення шаблону з рядка в точці використання краще, ніж зберігання його для ефективності, ризикуючи вирішити проблеми з одночасністю? я вам це дозволю. Мене бентежило те речення про фабричні методи та громадські конструктори, що, здається, є червоною оселедець з цієї теми.
Jason S

@ Джейсон S, ні, фабричні методи та відсутність конструкторів - це деякі із способів зменшити загрозу з'єднання з іншими потоками. Якщо єдиний спосіб отримати Matcher, що йде з моїм шаблоном, - це через p.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);краще.
Крістоф Руссі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.