¿Es seguro Java Regex Thread?

104

Tengo una función que usa Pattern#compiley Matcherpara buscar un patrón en una lista de cadenas.

Esta función se utiliza en varios subprocesos. Cada hilo tendrá un patrón único que se le pasará Pattern#compilecuando se cree el hilo. El número de subprocesos y patrones es dinámico, lo que significa que puedo agregar más Patterns y subprocesos durante la configuración.

¿Necesito poner una synchronizeen esta función si usa expresiones regulares? ¿Es seguro el regex en Java thread?

jmq
fuente

Respuestas:

132

, de la documentación de la API de Java para la clase Pattern

Las instancias de esta clase (Patrón) son inmutables y seguras para su uso por varios subprocesos simultáneos. Las instancias de la clase Matcher no son seguras para tal uso.

Si está buscando un código centrado en el rendimiento, intente restablecer la instancia de Matcher utilizando el método reset (), en lugar de crear nuevas instancias. Esto restablecería el estado de la instancia de Matcher, haciéndola utilizable para la siguiente operación de expresiones regulares. De hecho, es el estado mantenido en la instancia de Matcher el responsable de que no sea seguro para el acceso concurrente.

Vineet Reynolds
fuente
17
Los objetos de patrón son seguros para subprocesos, pero es posible que el compile()método no lo sea. Ha habido dos o tres errores a lo largo de los años que provocaron fallas en la compilación en entornos multiproceso. Recomendaría hacer la compilación en un bloque sincronizado.
Alan Moore
4
Sí, se han producido errores de simultaneidad en la clase Pattern y se agradece su consejo sobre el acceso sincronizado. Sin embargo, los desarrolladores originales de la clase Pattern tenían la intención de hacer que la clase Pattern fuera segura para subprocesos, y ese es el contrato en el que cualquier programador de Java debería poder confiar. Para ser franco, prefiero tener variables locales de subproceso y aceptar el impacto de rendimiento mínimo que confiar en el comportamiento seguro de subprocesos por contrato (a menos que haya visto el código). Como dicen, "Threading es fácil, la sincronización correcta es difícil".
Vineet Reynolds
1
Tenga en cuenta que la fuente de "Pattern" está en la distribución de Oracle JDK (según oracle.com/technetwork/java/faq-141681.html#A14 : "El SDK de Java 2, Standard Edition contiene un archivo llamado src.zip que contiene el código fuente para las clases públicas en el paquete java ") para que uno mismo pueda echar un vistazo rápido.
David Tonhofer
@DavidTonhofer Creo que nuestro último JDK puede tener el código correcto sin errores, pero dado que los archivos .class intermedios de Java pueden ser interpretados en cualquier plataforma por cualquier máquina virtual compatible, no puede estar seguro de que esas correcciones existen en ese tiempo de ejecución. Por supuesto, la mayoría de las veces sabe qué versión está ejecutando el servidor, pero es tedioso comprobar cada versión.
TWiStErRob
12

Seguridad de subprocesos con expresiones regulares en Java

RESUMEN:

La API de expresión regular de Java se ha diseñado para permitir que un único patrón compilado se comparta en varias operaciones de coincidencia.

Puede llamar de forma segura Pattern.matcher () en el mismo patrón desde diferentes subprocesos y utilizar de forma segura los emparejadores al mismo tiempo. Pattern.matcher () es seguro para construir comparadores sin sincronización. Aunque el método no está sincronizado, es interno a la clase Pattern, una variable volátil llamada compilada siempre se establece después de construir un patrón y se lee al comienzo de la llamada a matcher (). Esto obliga a cualquier hilo que se refiera al Patrón a "ver" correctamente el contenido de ese objeto.

Por otro lado, no debe compartir un Matcher entre diferentes hilos. O al menos, si alguna vez lo hizo, debería utilizar la sincronización explícita.

adatapost
fuente
2
@akf, por cierto, debes tener en cuenta que es un sitio de discusión (muy parecido a este). Consideraría que cualquier cosa que encuentre allí no es ni mejor ni peor que la información que encontrará aquí (es decir, no es La única palabra verdadera de James Gosling).
Bob Cross
3

Si bien debe recordar que la seguridad de los subprocesos también debe tener en cuenta el código circundante, parece que tiene suerte. El hecho de que los Matchers se creen utilizando el método de fábrica de Matcher de Pattern y carezcan de constructores públicos es una señal positiva. Asimismo, utiliza el método estático de compilación para crear el patrón que lo abarca .

Entonces, en resumen, si haces algo como el ejemplo:

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

deberías estar haciéndolo bastante bien.

Siga el ejemplo de código para mayor claridad: tenga en cuenta que este ejemplo implica fuertemente que el Matcher así creado es local de subproceso con el Patrón y la prueba. Es decir, no debe exponer el Matcher así creado a ningún otro hilo.

Francamente, ese es el riesgo de cualquier pregunta de seguridad de subprocesos. La realidad es que cualquier código puede convertirse en subproceso inseguro si se esfuerza lo suficiente. Afortunadamente, hay libros maravillosos que nos enseñan un montón de formas en las que podríamos arruinar nuestro código. Si nos mantenemos alejados de esos errores, reducimos en gran medida nuestra propia probabilidad de enhebrar problemas.

Bob Cross
fuente
@Jason S: la localidad del hilo es una forma muy sencilla de lograr la seguridad del hilo incluso si el código interno no es seguro para el hilo. Si solo un método pudiera acceder a un método en particular a la vez, ha aplicado la seguridad de subprocesos externamente.
Bob Cross
1
ok, entonces solo estás diciendo que volver a crear un patrón a partir de una cadena en el punto de uso, es mejor que almacenarlo para que sea eficiente, a riesgo de lidiar con problemas de concurrencia. te lo concedo. Estaba confundido con esa oración sobre métodos de fábrica y constructores públicos, que parece una pista falsa con este tema.
Jason S
@Jason S, no, los métodos de fábrica y la falta de constructores son algunas de las formas en que puede reducir la amenaza de acoplamiento con otros hilos. Si la única forma en que puede obtener el Matcher que va con mi Pattern es a través de p.matcher (), nadie más puede aplicar efectos secundarios en mi Matcher. Sin embargo, todavía puedo causarme problemas: si tengo un método público que devuelve ese Matcher, otro hilo podría llegar a él y afectarlo. En resumen, la concurrencia es difícil (en CUALQUIER idioma).
Bob Cross
2

Un vistazo rápido al código Matcher.javamuestra un montón de variables miembro, incluido el texto que se está comparando, matrices para grupos, algunos índices para mantener la ubicación y algunos booleans para otro estado. Todo esto apunta a un estado Matcherque no se comportaría bien si varios usuarios acceden a él Threads. También lo hace JavaDoc :

Las instancias de esta clase no son seguras para que las utilicen varios subprocesos simultáneos.

Esto solo es un problema si, como señala @Bob Cross, se esfuerza por permitir el uso de sus correos electrónicos Matcherseparados Thread. Si necesita hacer esto, y cree que la sincronización será un problema para su código, una opción que tiene es utilizar un ThreadLocalobjeto de almacenamiento para mantener un Matcherhilo de trabajo por.

akf
fuente
1

En resumen, puede reutilizar (mantener en variables estáticas) los patrones compilados y decirles que le proporcionen nuevos Matchers cuando sea necesario para validar esos patrones de expresiones regulares con alguna cadena

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

}

consulte http://zoomicon.wordpress.com/2012/06/01/validating-e-mails-using-regular-expressions-in-java/ (cerca del final) con respecto al patrón de RegEx utilizado anteriormente para validar correos electrónicos ( en caso de que no se ajuste a sus necesidades de validación de correo electrónico como se publica aquí)

George Birbilis
fuente
3
¡Gracias por publicar tu respuesta! Asegúrese de leer detenidamente las preguntas frecuentes sobre la autopromoción. Alguien podría ver esta respuesta y la publicación del blog vinculada y pensar que usted publicó la publicación del blog simplemente para poder vincularla desde aquí.
Andrew Barber
2
¿Por qué molestarse static {}? Puede alinear esa inicialización de variable y hacer el Pattern finaltambién.
TWiStErRob
1
Apoyo la opinión de TWiStErRob: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);es mejor.
Christophe Roussy