Como os métodos estáticos sincronizados funcionam em Java e posso usá-lo para carregar entidades do Hibernate?

179

Se eu tiver uma classe util com métodos estáticos que chamarão as funções do Hibernate para obter acesso básico aos dados. Eu estou querendo saber se fazer o método synchronizedé a abordagem correta para garantir a segurança do thread.

Quero que isso impeça o acesso de informações à mesma instância de banco de dados. No entanto, agora tenho certeza se o código a seguir está impedindo getObjectByIda chamada para todas as classes quando é chamado por uma classe específica.

public class Utils {
     public static synchronized Object getObjectById (Class objclass, Long id) {
           // call hibernate class
         Session session = new Configuration().configure().buildSessionFactory().openSession();
         Object obj = session.load(objclass, id);
         session.close();
         return obj;
     }

     // other static methods
}
tomate
fonte

Respostas:

136

Usando sincronizado em um bloqueio de método estático, você sincronizará os métodos e atributos da classe (em oposição aos métodos e atributos da instância)

Portanto, sua suposição está correta.

Gostaria de saber se tornar o método sincronizado é a abordagem correta para garantir a segurança do thread.

Na verdade não. Você deve deixar seu RDBMS fazer esse trabalho. Eles são bons nesse tipo de coisa.

A única coisa que você conseguirá sincronizando o acesso ao banco de dados é tornar seu aplicativo terrivelmente lento. Além disso, no código que você postou, você está construindo um Session Factory toda vez, dessa forma, seu aplicativo passará mais tempo acessando o banco de dados do que realizando o trabalho real.

Imagine o seguinte cenário:

Os clientes A e B tentam inserir informações diferentes no registro X da tabela T.

Com sua abordagem, a única coisa que você obtém é garantir que uma seja chamada após a outra, quando isso aconteceria de qualquer maneira no banco de dados, porque o RDBMS os impedirá de inserir meia informação de A e metade de B ao mesmo tempo . O resultado será o mesmo, mas apenas 5 vezes (ou mais) mais lento.

Provavelmente, seria melhor dar uma olhada no capítulo "Transações e simultaneidade" na documentação do Hibernate. Na maioria das vezes, os problemas que você está tentando resolver já foram resolvidos e de uma maneira muito melhor.

OscarRyz
fonte
1
Resposta muito útil! OBRIGADO! Portanto, o Hibernate cuida da cnocurrency por "bloqueio otimista". Então não há necessidade de usar métodos "sincronizados" para resolver qualquer simultaneidade de acesso a dados? Use métodos "sincronizados" somente se os dados não estiverem armazenados no banco de dados? ..quando você os usa ??
tomate
1
1) Eu acho que existem alguns meios de usar o bloqueio pessimista também. 2) Não, o RDBMS pode fazer esse trabalho. 3) Se os dados forem acessados ​​por vários threads ao mesmo tempo. 4) a sincronização é útil quando dois threads precisam compartilhar dados. Se eles não precisam, muito melhor!
OscarRyz 24/02/09
7
Qualquer restaurante de fast food usa multithread. Um segmento leva seu pedido e usa outro para prepará-lo e continua com o próximo cliente. O ponto de sincronização funciona apenas quando eles trocam informações para saber o que preparar. Seguir um modelo como esse realmente simplifica a vida.
OscarRyz 24/02/09
5
"a turma toda" não está bloqueada. A especificação da linguagem da máquina Java : For a class (static) method, the monitor associated with the Class object for the method's class is used. For an instance method, the monitor associated with this (the object for which the method was invoked) is used.Portanto, se um encadeamento entrar em um método estático, o mesmo objeto retornado pelo Object # getClass será bloqueado. Outros threads ainda podem acessar métodos de instância.
Martin Andersson
4
lol Acho que minhas próprias palavras também não estão corretas. Eu disse "Assim, se um thread entra em um método estático, o mesmo objeto retornado pelo Object # getClass está bloqueado". Não é tecnicamente correto. Longa história resumida para todas as pessoas curiosas: Para cada classe em seu aplicativo, existe um Classobjeto instanciado por um dos carregadores de classe de máquinas virtuais. Como todos os objetos, esse objeto também tem um Monitorassociado. E esse monitor é o que está sendo bloqueado.
Martin Andersson
233

Para abordar a questão de maneira mais geral ...

Lembre-se de que o uso de métodos sincronizados é realmente apenas uma abreviação (suponha que a classe seja SomeClass):

synchronized static void foo() {
    ...
}

é o mesmo que

static void foo() {
    synchronized(SomeClass.class) {
        ...
    }
}

e

synchronized void foo() {
    ...
}

é o mesmo que

void foo() {
    synchronized(this) {
        ...
    }
}

Você pode usar qualquer objeto como a trava. Se você deseja bloquear subconjuntos de métodos estáticos, pode

class SomeClass {
    private static final Object LOCK_1 = new Object() {};
    private static final Object LOCK_2 = new Object() {};
    static void foo() {
        synchronized(LOCK_1) {...}
    }
    static void fee() {
        synchronized(LOCK_1) {...}
    }
    static void fie() {
        synchronized(LOCK_2) {...}
    }
    static void fo() {
        synchronized(LOCK_2) {...}
    }
}

(para métodos não estáticos, você gostaria que os bloqueios fossem campos não estáticos)

Scott Stanchfield
fonte
9
Esses 4 principais blocos de código são dourados. Exatamente o que eu estava procurando. Obrigado.
Ryan Shillington
É correto que, se eu usar um bloqueio estático no método não estático, dois objetos da classe SomeClass não possam executar o bloco ao mesmo tempo?
Samuel
2
@ Samuel - Quase ... É mais sobre threads do que instâncias de objetos. Você está certo, pois as instâncias separadas do SomeClass usarão o mesmo bloqueio / monitor: aquele associado ao objeto Someclass.class. Portanto, se dois threads diferentes estavam processando duas instâncias diferentes de SomeClass, os dois não podiam executar ao mesmo tempo. No entanto, se um único thread chamasse um método em uma instância de SomeClass, e esse método chamasse um método na outra instância, nenhum bloqueio aconteceria.
Scott Stanchfield
@ScottStanchfield Você listou maneiras de sincronizar métodos, todos eles são equivalentes?
Bionix1441
1
@ Bionix1441 - É tudo sobre escopo. Cada mecanismo acima fornece um controle mais preciso do bloqueio. Primeiro, usando a própria instância bloqueando um método inteiro, depois a própria instância para bloquear seções dentro de um método e, em seguida, qualquer instância de objeto para bloquear seções.
Scott Stanchfield 30/03
17

Os métodos estáticos usam a classe como objeto de bloqueio, que é Utils.class no seu exemplo. Então sim, está tudo bem.

starblue
fonte
14

static synchronizedsignifica segurar a trava no Classobjeto da classe, onde synchronized significa segurar a trava no próprio objeto da classe. Isso significa que, se você estiver acessando um método sincronizado não estático em um encadeamento (de execução), ainda poderá acessar um método sincronizado estático usando outro encadeamento.

Portanto, não é possível acessar dois mesmos tipos de métodos (dois métodos estáticos ou dois métodos não estáticos) a qualquer momento por mais de um encadeamento.

prasad
fonte
10

Por que você deseja impor que apenas um único encadeamento possa acessar o banco de dados a qualquer momento?

É tarefa do driver do banco de dados implementar qualquer bloqueio necessário, assumindo umaConnection seja usada apenas por um encadeamento de cada vez!

Provavelmente, seu banco de dados é perfeitamente capaz de lidar com vários acessos paralelos

oxbow_lakes
fonte
Aposto que é / foi uma solução alternativa para algum problema transacional. Ou seja, a solução não resolve o verdadeiro problema
matt b
1
Eu não sabia disso .... Eu pensei que teria que implementar isso manualmente. Obrigado por apontar isso! :)
tomate
2

Se tem algo a ver com os dados do seu banco de dados, por que não utilizar o bloqueio de isolamento do banco de dados para obter isso?

Ray Lu
fonte
Eu não tenho nenhum fundo de banco de dados. Agora eu sei!! Obrigado por apontar isso! :)
tomate
2

Para responder à sua pergunta, sim: ele não synchronizedpode ser executado por mais de um thread por vez.

David Z
fonte
2

Como a synchronizedpalavra-chave Java funciona

Quando você adiciona a synchronizedpalavra-chave a um método estático, o método pode ser chamado apenas por um único thread por vez.

No seu caso, toda chamada de método irá:

  • crie um novo SessionFactory
  • crie um novo Session
  • buscar a entidade
  • retornar a entidade de volta para o chamador

No entanto, esses eram seus requisitos:

  • Quero que isso impeça o acesso a informações na mesma instância do banco de dados.
  • impedindo de getObjectByIdser chamado para todas as classes quando é chamado por uma classe específica

Portanto, mesmo que o getObjectByIdmétodo seja seguro para threads, a implementação está errada.

SessionFactory Melhores Práticas

O SessionFactorythread é seguro e é um objeto muito caro para criar, pois precisa analisar as classes de entidade e criar a representação interna do metamodelo da entidade.

Portanto, você não deve criar a chamada SessionFactorya cada getObjectByIdmétodo.

Em vez disso, você deve criar uma instância singleton para ela.

private static final SessionFactory sessionFactory = new Configuration()
    .configure()
    .buildSessionFactory();

O Sessiondeve sempre estar fechado

Você não fechar o Sessionem umfinally bloco, e isso pode vazar recursos de banco de dados se uma exceção é lançada quando o carregamento da entidade.

De acordo com o Session.loadmétodo, o JavaDoc pode lançar a HibernateExceptionse a entidade não puder ser encontrada no banco de dados.

Você não deve usar esse método para determinar se existe uma instância (use em get()vez disso). Use isso apenas para recuperar uma instância que você supõe existir, onde a inexistência seria um erro real.

É por isso que você precisa usar um finallybloco para fechar o Sessionseguinte:

public static synchronized Object getObjectById (Class objclass, Long id) {    
     Session session = null;
     try {
         session = sessionFactory.openSession();
         return session.load(objclass, id);
     } finally {
         if(session != null) {
             session.close(); 
         }
     }
 }

Impedindo o acesso multithread

No seu caso, você queria garantir que apenas um encadeamento tenha acesso a essa entidade específica.

Mas a synchronizedpalavra - chave impede apenas que dois threads chamem ogetObjectById concorrente. Se os dois threads chamarem esse método um após o outro, você ainda terá dois threads usando essa entidade.

Portanto, se você deseja bloquear um determinado objeto de banco de dados para que nenhum outro encadeamento possa modificá-lo, será necessário usar bloqueios de banco de dados.

A synchronizedpalavra-chave funciona apenas em uma única JVM. Se você tiver vários nós da web, isso não impedirá o acesso multiencadeado em várias JVMs.

O que você precisa fazer é usar LockModeType.PESSIMISTIC_READouLockModeType.PESSIMISTIC_WRITE ao aplicar as alterações no banco de dados, assim:

Session session = null;
EntityTransaction tx = null;

try {
    session = sessionFactory.openSession();

    tx = session.getTransaction();
    tx.begin();

    Post post = session.find(
        Post.class, 
        id, 
        LockModeType.LockModeType.PESSIMISTIC_READ
    );

    post.setTitle("High-Performance Java Perisstence");

    tx.commit();
} catch(Exception e) {
    LOGGER.error("Post entity could not be changed", e);
    if(tx != null) {
        tx.rollback(); 
    }
} finally {
    if(session != null) {
        session.close(); 
    }
}

Então, foi o que eu fiz:

  • Criei um novo EntityTransactione iniciei uma nova transação de banco de dados
  • Carreguei a Postentidade enquanto mantinha um bloqueio no registro do banco de dados associado
  • Mudei a Postentidade e comprometi a transação
  • No caso de um Exceptionser jogado, reverti a transação

Para obter mais detalhes sobre transações ACID e banco de dados, consulte este artigo também.

Vlad Mihalcea
fonte