Il thread Java Regex è sicuro?


104

Ho una funzione che usa Pattern#compilee a Matcherper cercare un pattern in un elenco di stringhe.

Questa funzione viene utilizzata in più thread. Ogni thread avrà un modello univoco passato al Pattern#compilemomento della creazione del thread. Il numero di thread e modelli è dinamico, il che significa che posso aggiungere più se Patternthread durante la configurazione.

Devo mettere un synchronizesu questa funzione se usa regex? Le espressioni regolari nel thread java sono sicure?

Risposte:


132

, dalla documentazione dell'API Java per la classe Pattern

Le istanze di questa classe (Pattern) non sono modificabili e possono essere utilizzate da più thread simultanei. Le istanze della classe Matcher non sono sicure per tale uso.

Se stai esaminando il codice incentrato sulle prestazioni, prova a reimpostare l'istanza Matcher utilizzando il metodo reset (), invece di creare nuove istanze. Ciò ripristinerebbe lo stato dell'istanza Matcher, rendendola utilizzabile per la successiva operazione regex. In effetti, è lo stato gestito nell'istanza Matcher che è responsabile del fatto che non sia sicuro per l'accesso simultaneo.


17
Gli oggetti pattern sono thread-safe, ma il compile()metodo potrebbe non esserlo. Ci sono stati due o tre bug nel corso degli anni che hanno causato il fallimento della compilazione in ambienti multithread. Consiglierei di fare la compilazione in un blocco sincronizzato.
Alan Moore

4
Sì, sono stati sollevati bug di concorrenza nella classe Pattern e il tuo consiglio di accesso sincronizzato è apprezzato. Tuttavia, gli sviluppatori originali della classe Pattern intendevano rendere la classe Pattern sicura per i thread, e questo è il contratto su cui qualsiasi programmatore Java dovrebbe poter fare affidamento. Ad essere sincero, preferirei avere variabili locali del thread e accettare il minimo impatto sulle prestazioni piuttosto che fare affidamento su un comportamento thread-safe per contratto (a meno che non abbia visto il codice). Come si suol dire "Il threading è facile, la sincronizzazione corretta è difficile".
Vineet Reynolds,

1
Tieni presente che l'origine di "Pattern" si trova nella distribuzione Oracle JDK (secondo oracle.com/technetwork/java/faq-141681.html#A14 : "Java 2 SDK, Standard Edition contiene un file chiamato src.zip che contiene il codice sorgente per le classi pubbliche nel pacchetto java ") così si può dare una rapida occhiata a se stessi.
David Tonhofer

@DavidTonhofer Penso che il nostro ultimo JDK possa avere il codice corretto senza bug, ma poiché i file .class intermedi di Java possono essere interpretati su qualsiasi piattaforma da qualsiasi VM compatibile, non puoi essere sicuro che quelle correzioni esistano in quel runtime. Ovviamente la maggior parte delle volte sai quale versione è in esecuzione sul server, ma è noioso controllare ogni singola versione.
TWiStErRob

12

Thread-safety con espressioni regolari in Java

SOMMARIO:

L'API delle espressioni regolari Java è stata progettata per consentire la condivisione di un singolo modello compilato tra più operazioni di corrispondenza.

Puoi chiamare in sicurezza Pattern.matcher () sullo stesso modello da thread diversi e utilizzare in modo sicuro i matcher contemporaneamente. Pattern.matcher () è sicuro per costruire matcher senza sincronizzazione. Sebbene il metodo non sia sincronizzato, interno alla classe Pattern, una variabile volatile chiamata compilata viene sempre impostata dopo la costruzione di un modello e letta all'inizio della chiamata a matcher (). Ciò impone a qualsiasi thread che fa riferimento al Pattern di "vedere" correttamente il contenuto di quell'oggetto.

D'altra parte, non dovresti condividere un Matcher tra thread diversi. O almeno, se mai l'hai fatto, dovresti usare la sincronizzazione esplicita.


2
@akf, BTW, dovresti notare che questo è un sito di discussione (molto simile a questo). Considero tutto ciò che trovi non migliore o peggiore delle informazioni che troverai qui (cioè, non è The One True Word From James Gosling).
Bob Cross

3

Sebbene sia necessario ricordare che la sicurezza dei thread deve tenere in considerazione anche il codice circostante, sembra che tu sia fortunato. Il fatto che i Matcher siano creati utilizzando il metodo matcher factory del Pattern e non abbiano costruttori pubblici è un segno positivo. Allo stesso modo, usate il metodo statico di compilazione per creare il Pattern che comprende .

Quindi, in breve, se fai qualcosa di simile all'esempio:

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

dovresti fare abbastanza bene.

Follow-up all'esempio di codice per chiarezza: si noti che questo esempio implica fortemente che il Matcher così creato è thread-local con il Pattern e il test. Cioè, non dovresti esporre il Matcher così creato a nessun altro thread.

Francamente, questo è il rischio di qualsiasi domanda sulla sicurezza dei thread. La realtà è che qualsiasi codice può essere reso non sicuro per i thread se ci provi abbastanza. Fortunatamente, ci sono libri meravigliosi che ci insegnano un sacco di modi in cui potremmo rovinare il nostro codice. Se ci teniamo lontani da questi errori, riduciamo notevolmente la nostra probabilità di problemi di threading.


@ Jason S: la località del thread è un modo molto semplice per ottenere la sicurezza del thread anche se il codice interno non è thread-safe. Se solo un metodo alla volta potesse accedere a un particolare metodo, hai applicato la sicurezza del thread esternamente.
Bob Cross

1
ok, quindi stai solo dicendo che ricreare un pattern da una stringa nel punto di utilizzo è meglio che memorizzarlo per essere efficiente, a rischio di affrontare problemi di concorrenza? te lo garantisco. Ero confuso con quella frase sui metodi di fabbrica e sui costruttori pubblici, che sembra una falsa pista con questo argomento.
Jason S

@ Jason S, no, i metodi di fabbrica e la mancanza di costruttori sono alcuni dei modi in cui puoi ridurre la minaccia di accoppiamento con altri thread. Se l'unico modo per ottenere il Matcher che va con il mio Pattern è tramite p.matcher (), nessun altro può avere effetti collaterali sul mio Matcher. Tuttavia, posso ancora causare problemi a me stesso: se ho un metodo pubblico che restituisce quel Matcher, un altro thread potrebbe raggiungerlo e influenzarlo. In breve, la concorrenza è difficile (in QUALSIASI lingua).
Bob Cross

2

Una rapida occhiata al codice per Matcher.javamostra un mucchio di variabili membro incluso il testo che viene trovato, array per i gruppi, alcuni indici per mantenere la posizione e alcuni booleanper un altro stato. Tutto ciò indica uno stato Matcherche non si comporterebbe bene se vi accedessero più Threads. Così fa il JavaDoc :

Le istanze di questa classe non possono essere utilizzate da più thread simultanei.

Questo è un problema solo se, come sottolinea @Bob Cross, fai di tutto per consentire l'uso del tuo Matcherin Threads separati . Se hai bisogno di farlo e pensi che la sincronizzazione sarà un problema per il tuo codice, un'opzione che hai è usare un ThreadLocaloggetto di archiviazione per mantenere un Matcherthread funzionante.


1

Per riassumere, puoi riutilizzare (mantenere nelle variabili statiche) i Pattern compilati e dire loro di darti nuovi Matcher quando necessario per convalidare quei pattens regex contro qualche stringa

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

}

vedi http://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/ (verso la fine) per quanto riguarda il pattern RegEx usato sopra per la convalida delle e-mail ( nel caso in cui non si adatti alle esigenze di convalida e-mail come è pubblicato qui)


3
Grazie per aver pubblicato la tua risposta! Assicurati di leggere attentamente le FAQ sull'autopromozione . Qualcuno potrebbe vedere questa risposta e il post del blog collegato al link e pensare che tu abbia pubblicato il post del blog semplicemente in modo da poterlo collegare da qui.
Andrew Barber,

2
Perché preoccuparsi static {}? Puoi inline l'inizializzazione della variabile e fare Pattern finalanche il file.
TWiStErRob

1
Secondo il parere di TWiStErRob: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);è meglio.
Christophe Roussy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.