Java Regex Thread est-il sûr?

104

J'ai une fonction qui utilise Pattern#compileet a Matcherpour rechercher une liste de chaînes pour un motif.

Cette fonction est utilisée dans plusieurs threads. Chaque thread aura un modèle unique transmis au Pattern#compilelorsque le thread est créé. Le nombre de threads et de modèles est dynamique, ce qui signifie que je peux ajouter plus de Patternthreads et de threads pendant la configuration.

Dois-je mettre un synchronizesur cette fonction si elle utilise regex? Est-ce que regex dans les threads Java est sûr?

jmq
la source

Réponses:

132

Oui , à partir de la documentation de l'API Java pour la classe Pattern

Les instances de cette classe (Pattern) sont immuables et peuvent être utilisées en toute sécurité par plusieurs threads simultanés. Les instances de la classe Matcher ne sont pas sûres pour une telle utilisation.

Si vous recherchez du code centré sur les performances, essayez de réinitialiser l'instance Matcher à l'aide de la méthode reset (), au lieu de créer de nouvelles instances. Cela réinitialiserait l'état de l'instance Matcher, le rendant utilisable pour la prochaine opération regex. En fait, c'est l'état maintenu dans l'instance Matcher qui est responsable de sa non-sécurité pour l'accès simultané.

Vineet Reynolds
la source
17
Les objets de modèle sont thread-safe, mais la compile()méthode peut ne pas l'être. Il y a eu deux ou trois bogues au fil des ans qui ont provoqué l'échec de la compilation dans les environnements multithread. Je recommanderais de faire la compilation dans un bloc synchronisé.
Alan Moore le
4
Oui, des bogues de concurrence ont été soulevés dans la classe Pattern, et vos conseils d'accès synchronisé sont appréciés. Cependant, les développeurs originaux de la classe Pattern avaient l'intention de rendre la classe Pattern comme thread-safe, et c'est le contrat sur lequel tout programmeur Java devrait pouvoir s'appuyer. Pour être franc, je préfère avoir des variables locales de thread et accepter la performance minimale que de compter sur un comportement thread-safe par contrat (sauf si j'ai vu le code). Comme on dit: "Threading est facile, une synchronisation correcte est difficile".
Vineet Reynolds
1
Notez que la source de "Pattern" se trouve dans la distribution Oracle JDK (selon oracle.com/technetwork/java/faq-141681.html#A14 : "Le SDK Java 2, Standard Edition lui-même contient un fichier appelé src.zip qui contient le code source des classes publiques dans le paquet java ") afin que l'on puisse y jeter un coup d'œil.
David Tonhofer
@DavidTonhofer Je pense que notre dernier JDK peut avoir le bon code sans bogue, mais comme les fichiers .class intermédiaires de Java peuvent être interprétés sur n'importe quelle plate-forme par n'importe quelle machine virtuelle compatible, vous ne pouvez pas être sûr que ces correctifs existent dans ce runtime. Bien sûr, la plupart du temps, vous savez quelle version le serveur exécute, mais il est fastidieux de vérifier chaque version.
TWiStErRob
12

Sécurité des threads avec des expressions régulières en Java

RÉSUMÉ:

L'API d'expression régulière Java a été conçue pour permettre à un seul modèle compilé d'être partagé entre plusieurs opérations de correspondance.

Vous pouvez appeler Pattern.matcher () en toute sécurité sur le même modèle à partir de différents threads et utiliser en toute sécurité les correspondants simultanément. Pattern.matcher () est sûr de construire des matchers sans synchronisation. Bien que la méthode ne soit pas synchronisée, interne à la classe Pattern, une variable volatile appelée compiled est toujours définie après la construction d'un pattern et lue au début de l'appel à matcher (). Cela force tout thread faisant référence au modèle à "voir" correctement le contenu de cet objet.

D'un autre côté, vous ne devriez pas partager un Matcher entre différents threads. Ou du moins, si jamais vous l'avez fait, vous devriez utiliser la synchronisation explicite.

adatapost
la source
2
@akf, BTW, vous devriez noter que c'est un site de discussion (un peu comme celui-ci). Je considère que tout ce que vous y trouvez n'est pas meilleur ou pire que les informations que vous trouverez ici (c'est-à-dire que ce n'est pas le seul vrai mot de James Gosling).
Bob Cross
3

Même si vous devez vous rappeler que la sécurité des threads doit également prendre en compte le code environnant, vous semblez avoir de la chance. Le fait que les Matchers soient créés à l'aide de la méthode de fabrique matcher du Pattern et qu'ils manquent de constructeurs publics est un signe positif. De même, vous utilisez la méthode statique de compilation pour créer le modèle englobant .

Donc, en bref, si vous faites quelque chose comme l'exemple:

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

vous devriez vous débrouiller plutôt bien.

Suivi de l'exemple de code pour plus de clarté: notez que cet exemple implique fortement que le Matcher ainsi créé est thread-local avec le Pattern et le test. Par exemple, vous ne devez pas exposer le Matcher ainsi créé à d'autres threads.

Franchement, c'est le risque de toute question de sécurité des threads. La réalité est que tout code peut être rendu non sûr pour les threads si vous essayez assez fort. Heureusement, il existe de merveilleux livres qui nous apprennent tout un tas de façons de ruiner notre code. Si nous restons à l'écart de ces erreurs, nous réduisons considérablement notre propre probabilité de problèmes de threading.

Bob Cross
la source
@Jason S: la localisation des threads est un moyen très simple de garantir la sécurité des threads même si le code interne n'est pas thread-safe. Si une seule méthode pouvait accéder à une méthode particulière à la fois, vous avez appliqué la sécurité des threads en externe.
Bob Cross
1
ok, alors vous dites simplement que recréer un motif à partir d'une chaîne au point d'utilisation, est mieux que de le stocker pour être efficace, au risque de traiter des problèmes de concurrence? je vous l'accorderai. J'ai été confondu avec cette phrase sur les méthodes d'usine et les constructeurs publics, qui semble être un hareng rouge avec ce sujet.
Jason S
@Jason S, non, les méthodes d'usine et le manque de constructeurs sont quelques-uns des moyens de réduire la menace de couplage avec d'autres threads. Si le seul moyen d'obtenir le Matcher qui va avec mon Pattern est via p.matcher (), personne d'autre ne peut affecter mon Matcher. Cependant, je peux toujours me causer des problèmes: si j'ai une méthode publique qui renvoie ce Matcher, un autre thread pourrait l'atteindre et le modifier. En bref, la concurrence est difficile (dans N'IMPORTE QUELLE langue).
Bob Cross
2

Un rapide coup d'œil au code pour Matcher.javamontre un tas de variables membres, y compris le texte qui est mis en correspondance, des tableaux pour les groupes, quelques index pour maintenir l'emplacement et quelques booleans pour un autre état. Tout cela pointe vers un état Matcherqui ne se comporterait pas bien s'il était accédé par plusieurs Threads. Tout comme le JavaDoc :

Les instances de cette classe ne sont pas sûres pour une utilisation par plusieurs threads simultanés.

Ce n'est un problème que si, comme le souligne @Bob Cross, vous faites tout votre possible pour autoriser l'utilisation de votre Matcherdans des Threads séparés . Si vous devez faire cela et que vous pensez que la synchronisation sera un problème pour votre code, une option que vous avez est d'utiliser un ThreadLocalobjet de stockage pour maintenir un Matcherthread de travail par thread.

akf
la source
1

Pour résumer, vous pouvez réutiliser (conserver dans des variables statiques) le (s) Pattern (s) compilé (s) et leur dire de vous donner de nouveaux Matchers en cas de besoin pour valider ces motifs regex par rapport à une chaîne

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

}

voir http://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/ (vers la fin) concernant le modèle RegEx utilisé ci-dessus pour valider les e-mails ( au cas où cela ne correspond pas à vos besoins de validation par e-mail tel qu'il est affiché ici)

George Birbilis
la source
3
Merci d'avoir publié votre réponse! Veuillez lire attentivement la FAQ sur l'auto-promotion . Quelqu'un peut voir cette réponse et l'article de blog lié au blog et penser que vous l'avez simplement publié pour que vous puissiez y accéder à partir d'ici.
Andrew Barber
2
Pourquoi s'en soucier static {}? Vous pouvez intégrer cette initialisation de variable et créer le Pattern finalfichier.
TWiStErRob
1
J'appuie l'opinion de TWiStErRob: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);c'est mieux.
Christophe Roussy