Ist Java Regex Thread sicher?

104

Ich habe eine Funktion, die verwendet Pattern#compileund Matchereine Liste von Zeichenfolgen nach einem Muster durchsucht.

Diese Funktion wird in mehreren Threads verwendet. Jeder Thread hat ein eindeutiges Muster, das Pattern#compilebeim Erstellen des Threads übergeben wird. Die Anzahl der Threads und Muster ist dynamisch, was bedeutet, dass ich Patternwährend der Konfiguration weitere s und Threads hinzufügen kann .

Muss ich synchronizediese Funktion aktivieren, wenn sie Regex verwendet? Ist Regex im Java-Thread sicher?

jmq
quelle

Antworten:

132

Ja , aus der Java-API-Dokumentation für die Pattern-Klasse

Instanzen dieser Klasse (Muster) sind unveränderlich und können von mehreren gleichzeitigen Threads verwendet werden. Instanzen der Matcher-Klasse sind für eine solche Verwendung nicht sicher.

Wenn Sie leistungsorientierten Code betrachten, versuchen Sie, die Matcher-Instanz mithilfe der reset () -Methode zurückzusetzen, anstatt neue Instanzen zu erstellen. Dies würde den Status der Matcher-Instanz zurücksetzen und sie für die nächste Regex-Operation verwenden. Tatsächlich ist der in der Matcher-Instanz verwaltete Status dafür verantwortlich, dass er für den gleichzeitigen Zugriff nicht sicher ist.

Vineet Reynolds
quelle
17
Musterobjekte sind threadsicher, die compile()Methode jedoch möglicherweise nicht. Im Laufe der Jahre gab es zwei oder drei Fehler, die dazu führten, dass die Kompilierung in Multithread-Umgebungen fehlschlug. Ich würde empfehlen, die Kompilierung in einem synchronisierten Block durchzuführen.
Alan Moore
4
Ja, in der Pattern-Klasse sind Parallelitätsfehler aufgetreten, und Ihr Ratschlag zum synchronisierten Zugriff wird geschätzt. Die ursprünglichen Entwickler der Pattern-Klasse beabsichtigten jedoch, die Pattern-Klasse als Thread-sicher zu machen, und dies ist der Vertrag, auf den sich jeder Java-Programmierer verlassen sollte. Um ehrlich zu sein, ich hätte lieber lokale Thread-Variablen und würde den minimalen Leistungseinbruch akzeptieren, als mich vertraglich auf threadsicheres Verhalten zu verlassen (es sei denn, ich habe den Code gesehen). Wie sie sagen "Threading ist einfach, korrekte Synchronisation ist schwierig".
Vineet Reynolds
1
Beachten Sie, dass sich die Quelle von "Pattern" in der Oracle JDK-Distribution befindet (laut oracle.com/technetwork/java/faq-141681.html#A14 : "Das Java 2 SDK, Standard Edition selbst enthält eine Datei namens src.zip that enthält den Quellcode für die öffentlichen Klassen im Java-Paket "), damit man einen kurzen Blick darauf werfen kann.
David Tonhofer
@DavidTonhofer Ich denke, unser neuestes JDK hat möglicherweise den richtigen fehlerfreien Code, aber da Javas .class-Zwischendateien auf jeder Plattform von jeder kompatiblen VM interpretiert werden können, können Sie nicht sicher sein, ob diese Korrekturen in dieser Laufzeit vorhanden sind. Natürlich wissen Sie die meiste Zeit, welche Version auf dem Server ausgeführt wird, aber es ist mühsam, jede einzelne Version zu überprüfen.
TWiStErRob
12

Thread-Sicherheit mit regulären Ausdrücken in Java

ZUSAMMENFASSUNG:

Die Java-API für reguläre Ausdrücke wurde so konzipiert, dass ein einzelnes kompiliertes Muster für mehrere Übereinstimmungsvorgänge gemeinsam genutzt werden kann.

Sie können Pattern.matcher () für dasselbe Muster sicher aus verschiedenen Threads aufrufen und die Matcher sicher gleichzeitig verwenden. Pattern.matcher () ist sicher, Matcher ohne Synchronisation zu erstellen . Obwohl die Methode innerhalb der Pattern-Klasse nicht synchronisiert ist, wird eine flüchtige Variable namens compiled immer nach dem Erstellen eines Musters festgelegt und zu Beginn des Aufrufs von matcher () gelesen . Dies zwingt jeden Thread, der sich auf das Muster bezieht, den Inhalt dieses Objekts korrekt zu "sehen".

Auf der anderen Seite sollten Sie einen Matcher nicht für verschiedene Threads freigeben. Wenn Sie dies jemals getan haben, sollten Sie zumindest eine explizite Synchronisierung verwenden.

Adatapost
quelle
2
@akf, übrigens, Sie sollten beachten, dass dies eine Diskussionsseite ist (ähnlich wie diese). Ich würde alles, was Sie dort finden, nicht besser oder schlechter finden als Informationen, die Sie hier finden würden (dh es ist nicht das einzig wahre Wort von James Gosling).
Bob Cross
3

Während Sie sich daran erinnern müssen, dass die Thread-Sicherheit auch den umgebenden Code berücksichtigen muss, scheinen Sie Glück zu haben. Die Tatsache, dass Matcher mit der Matcher- Factory-Methode des Musters erstellt werden und keine öffentlichen Konstruktoren haben, ist ein positives Zeichen. Ebenso verwenden Sie die statische Kompilierungsmethode , um das umfassende Muster zu erstellen .

Kurz gesagt, wenn Sie so etwas wie das Beispiel machen:

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

du solltest es ziemlich gut machen.

Follow-up zum Codebeispiel zur Verdeutlichung: Beachten Sie, dass dieses Beispiel stark impliziert, dass der so erstellte Matcher mit dem Muster und dem Test threadlokal ist. Das heißt, Sie sollten den so erstellten Matcher keinem anderen Thread aussetzen.

Ehrlich gesagt, das ist das Risiko von Fragen zur Thread-Sicherheit. Die Realität ist, dass jeder Code threadsicher gemacht werden kann, wenn Sie sich genug anstrengen. Glücklicherweise gibt es wundervolle Bücher , die uns eine ganze Reihe von Möglichkeiten lehren, wie wir unseren Code ruinieren können. Wenn wir uns von diesen Fehlern fernhalten, reduzieren wir unsere eigene Wahrscheinlichkeit von Threading-Problemen erheblich.

Bob Cross
quelle
@ Jason S: Die Thread-Lokalität ist ein sehr einfacher Weg, um Thread-Sicherheit zu erreichen, selbst wenn der interne Code nicht thread-sicher ist. Wenn jeweils nur eine Methode auf eine bestimmte Methode zugreifen kann, haben Sie die Thread-Sicherheit extern erzwungen.
Bob Cross
1
OK, Sie sagen also nur, dass es besser ist, ein Muster aus einer Zeichenfolge am Verwendungsort neu zu erstellen, als es effizient zu speichern, wenn das Risiko besteht, dass Parallelitätsprobleme auftreten? Ich werde dir das gewähren. Ich war verwirrt mit diesem Satz über Fabrikmethoden und öffentliche Konstrukteure, der wie ein roter Hering ohne dieses Thema erscheint.
Jason S
@ Jason S, nein, die Factory-Methoden und das Fehlen von Konstruktoren sind einige der Möglichkeiten, wie Sie die Gefahr der Kopplung mit anderen Threads verringern können. Wenn Sie den Matcher, der zu meinem Muster gehört, nur über p.matcher () erhalten können, kann niemand anderes meinen Matcher als Nebeneffekt verwenden. Ich kann mir jedoch immer noch Probleme bereiten: Wenn ich eine öffentliche Methode habe, die diesen Matcher zurückgibt, könnte ein anderer Thread darauf zugreifen und ihn als Nebeneffekt verwenden. Kurz gesagt, Parallelität ist schwierig (in JEDER Sprache).
Bob Cross
2

Ein kurzer Blick auf den Code für Matcher.javazeigt eine Reihe von Mitgliedsvariablen, einschließlich des übereinstimmenden Texts, Arrays für Gruppen, einige Indizes zum Verwalten des Speicherorts und einige booleans für andere Status. Dies alles deutet auf einen Zustand hin Matcher, der sich nicht gut verhält, wenn mehrere darauf zugreifen Threads. So auch das JavaDoc :

Instanzen dieser Klasse können nicht von mehreren gleichzeitigen Threads verwendet werden.

Dies ist nur dann ein Problem, wenn Sie, wie @Bob Cross hervorhebt, alles daran setzen, Ihre Verwendung Matcherin separaten Threads zuzulassen . Wenn Sie dies tun müssen und glauben, dass die Synchronisierung ein Problem für Ihren Code darstellt, können Sie ein ThreadLocalSpeicherobjekt verwenden, um einen Matcherpro Arbeitsthread zu verwalten.

akf
quelle
1

Zusammenfassend lässt sich sagen, dass Sie die kompilierten Muster wiederverwenden (in statischen Variablen behalten) und ihnen mitteilen können, dass sie Ihnen bei Bedarf neue Matcher geben sollen, um diese Regex-Pattens anhand einer Zeichenfolge zu validieren

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

}

siehe http://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/ (am Ende) in Bezug auf das Muster RegEx oben verwendete für die Validierung von E-Mail ( falls es nicht den Anforderungen für die E-Mail-Validierung entspricht, wie hier veröffentlicht)

George Birbilis
quelle
3
Vielen Dank für Ihre Antwort! Bitte lesen Sie die FAQ zur Eigenwerbung sorgfältig durch. Jemand könnte diese Antwort und den verlinkten Blog-Beitrag sehen und denken, Sie hätten den Blog-Beitrag nur gepostet, damit Sie von hier aus darauf verlinken können.
Andrew Barber
2
Warum sich damit beschäftigen static {}? Sie können diese Variableninitialisierung einbinden und Pattern finalauch durchführen.
TWiStErRob
1
Ich stimme der Meinung von TWiStErRob zu: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);ist besser.
Christophe Roussy