O Java Regex Thread é seguro?

104

Eu tenho uma função que usa Pattern#compilee Matcherpara pesquisar uma lista de strings para um padrão.

Esta função é usada em vários threads. Cada thread terá um padrão exclusivo passado para o Pattern#compilequando o thread for criado. O número de threads e padrões são dinâmicos, o que significa que posso adicionar mais se Patternthreads durante a configuração.

Preciso colocar um synchronizenesta função se ela usa regex? O regex no thread de java é seguro?

jmq
fonte

Respostas:

132

Sim , da documentação da API Java para a classe Pattern

As instâncias desta classe (Padrão) são imutáveis ​​e seguras para uso por vários threads simultâneos. As instâncias da classe Matcher não são seguras para tal uso.

Se você estiver observando um código centrado no desempenho, tente redefinir a instância Matcher usando o método reset (), em vez de criar novas instâncias. Isso redefiniria o estado da instância Matcher, tornando-a utilizável para a próxima operação regex. Na verdade, é o estado mantido na instância do Matcher que é responsável por não ser seguro para acesso simultâneo.

Vineet Reynolds
fonte
17
Objetos padrão são seguros para thread, mas o compile()método pode não ser. Houve dois ou três bugs ao longo dos anos que fizeram com que a compilação falhasse em ambientes multithread. Eu recomendaria fazer a compilação em um bloco sincronizado.
Alan Moore,
4
Sim, foram levantados bugs de simultaneidade na classe Pattern e agradecemos seus conselhos sobre o acesso sincronizado. No entanto, os desenvolvedores originais da classe Pattern pretendiam tornar a classe Pattern segura para encadeamentos, e esse é o contrato em que qualquer programador Java deve confiar. Para ser franco, prefiro ter variáveis ​​locais de thread e aceitar o impacto mínimo de desempenho do que confiar no comportamento seguro de thread por contrato (a menos que tenha visto o código). Como se costuma dizer "Threading é fácil, sincronização correta é difícil".
Vineet Reynolds,
1
Observe que a fonte de "Pattern" está na distribuição Oracle JDK (de acordo com oracle.com/technetwork/java/faq-141681.html#A14 : "O Java 2 SDK, Standard Edition em si contém um arquivo chamado src.zip que contém o código-fonte para as classes públicas no pacote java ") para que possamos dar uma olhada rápida.
David Tonhofer
@DavidTonhofer Acho que nosso JDK mais recente pode ter o código correto sem bugs, mas como os arquivos .class intermediários do Java podem ser interpretados em qualquer plataforma por qualquer VM compatível, você não pode ter certeza de que essas correções existem naquele tempo de execução. É claro que na maioria das vezes você sabe qual versão o servidor está executando, mas é tedioso verificar todas as versões.
TWiStErRob
12

Thread-safety com expressões regulares em Java

RESUMO:

A API de expressão regular Java foi projetada para permitir que um único padrão compilado seja compartilhado entre várias operações de correspondência.

Você pode chamar Pattern.matcher () com segurança no mesmo padrão de threads diferentes e usar os matchers simultaneamente. Pattern.matcher () é seguro para construir matchers sem sincronização. Embora o método não seja sincronizado, interno à classe Pattern, uma variável volátil chamada compilada é sempre definida após a construção de um padrão e lida no início da chamada para matcher (). Isso força qualquer thread que se refira ao Padrão a "ver" corretamente o conteúdo desse objeto.

Por outro lado, você não deve compartilhar um Matcher entre diferentes threads. Ou pelo menos, se você já fez, você deve usar a sincronização explícita.

adatapost
fonte
2
@akf, BTW, você deve notar que este é um site de discussão (muito parecido com este). Eu consideraria qualquer coisa que você encontrar lá nem melhor ou pior do que as informações que você encontraria aqui (ou seja, não é a única palavra verdadeira de James Gosling).
Bob Cross,
3

Embora você precise lembrar que a segurança do thread também deve levar em conta o código ao redor, você parece estar com sorte. O fato de que Matchers são criados usando o método de fábrica de matcher do Pattern e não possuem construtores públicos é um sinal positivo. Da mesma forma, você usa o método estático de compilação para criar o padrão abrangente .

Então, em resumo, se você fizer algo como o exemplo:

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

você deve estar indo muito bem.

Siga o exemplo de código para maior clareza: observe que este exemplo implica fortemente que o Matcher assim criado é thread-local com o Padrão e o teste. Ou seja, você não deve expor o Matcher assim criado a quaisquer outros threads.

Francamente, esse é o risco de qualquer questão de thread-safety. A realidade é que qualquer código pode se tornar inseguro para thread se você tentar o suficiente. Felizmente, existem livros maravilhosos que nos ensinam uma série de maneiras de arruinar nosso código. Se ficarmos longe desses erros, reduziremos muito nossa própria probabilidade de problemas de encadeamento.

Bob Cross
fonte
@Jason S: localidade de thread é uma maneira muito direta de obter segurança de thread, mesmo se o código interno não for seguro para thread. Se apenas um método pudesse acessar um método específico por vez, você reforçou a segurança de thread externamente.
Bob Cross,
1
ok, então você está apenas dizendo que recriar um padrão de uma string no ponto de uso, é melhor do que armazená-lo para ser eficiente, correndo o risco de lidar com problemas de simultaneidade? eu vou te conceder isso. Fiquei confuso com aquela frase sobre métodos de fábrica e construtores públicos, que parece uma pista falsa sem esse tópico.
Jason S
@Jason S, não, os métodos de fábrica e a falta de construtores são algumas das maneiras de reduzir a ameaça de acoplamento com outros threads. Se a única maneira de obter o Matcher que acompanha meu padrão é por meio de p.matcher (), ninguém mais pode afetar meu Matcher. No entanto, ainda posso causar problemas para mim: se eu tiver um método público que retorne esse Matcher, outro thread pode pegá-lo e afetá-lo. Resumindo, a simultaneidade é difícil (em QUALQUER idioma).
Bob Cross
2

Uma rápida olhada no código de Matcher.javamostra um monte de variáveis ​​de membro, incluindo o texto que está sendo correspondido, matrizes para grupos, alguns índices para manter a localização e alguns booleans para outro estado. Tudo isso aponta para um stateful Matcherque não se comportaria bem se acessado por vários Threads. O mesmo acontece com o JavaDoc :

As instâncias desta classe não são seguras para uso por vários threads simultâneos.

Isso só é um problema se, como @Bob Cross aponta, você se esforça para permitir o uso de seus programas Matcherseparados Thread. Se você precisar fazer isso e achar que a sincronização será um problema para o seu código, uma opção que você tem é usar um ThreadLocalobjeto de armazenamento para manter um Matcherpor thread de trabalho.

akf
fonte
1

Para resumir, você pode reutilizar (manter em variáveis ​​estáticas) o (s) padrão (ões) compilado (s) e dizer a eles para fornecerem novos Matchers quando necessário para validar aqueles pattens regex contra alguma string

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/ (próximo ao final) sobre o padrão RegEx usado acima para validar e-mails ( caso não se enquadre nas necessidades de validação de e-mail conforme postado aqui)

George Birbilis
fonte
3
Obrigado por postar sua resposta! Certifique-se de ler as Perguntas frequentes sobre autopromoção com atenção. Alguém pode ver esta resposta e a postagem com link para o blog e pensar que você postou a postagem apenas para poder criar um link a partir daqui.
Andrew Barber
2
Por que se preocupar static {}? Você pode embutir essa inicialização de variável e fazer o Pattern finaltambém.
TWiStErRob
1
Eu concordo com a opinião de TWiStErRob: private static final Pattern emailPattern = Pattern.compile(EMAIL_PATTERN);é melhor.
Christophe Roussy