¿Cómo funcionan los métodos estáticos sincronizados en Java y puedo usarlos para cargar entidades de Hibernate?

179

Si tengo una clase util con métodos estáticos que llamarán a las funciones de Hibernate para lograr el acceso básico a datos. Me pregunto si hacer el método synchronizedes el enfoque correcto para garantizar la seguridad del hilo.

Quiero que esto evite el acceso de información a la misma instancia de DB. Sin embargo, ahora estoy seguro de si el siguiente código impide que getObjectByIdse invoque para todas las clases cuando una clase en particular lo invoca.

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
fuente

Respuestas:

136

Al usar sincronizado en un bloqueo de método estático, sincronizará los métodos y atributos de clase (en oposición a los métodos y atributos de instancia)

Entonces su suposición es correcta.

Me pregunto si hacer que el método esté sincronizado es el enfoque correcto para garantizar la seguridad del hilo.

Realmente no. Debería dejar que su RDBMS haga ese trabajo en su lugar. Son buenos en este tipo de cosas.

Lo único que obtendrá al sincronizar el acceso a la base de datos es hacer que su aplicación sea terriblemente lenta. Además, en el código que publicó está creando una Fábrica de sesiones cada vez, de esa manera, su aplicación pasará más tiempo accediendo a la base de datos que realizando el trabajo real.

Imagine el siguiente escenario:

Los clientes A y B intentan insertar información diferente en el registro X de la tabla T.

Con su enfoque, lo único que está obteniendo es asegurarse de que se llame uno después del otro, cuando esto sucedería de todos modos en la base de datos, porque el RDBMS les impedirá insertar la mitad de la información de A y la otra mitad de B al mismo tiempo . El resultado será el mismo pero solo 5 veces (o más) más lento.

Probablemente sería mejor echar un vistazo al capítulo "Transacciones y concurrencia" en la documentación de Hibernate. La mayoría de las veces, los problemas que intenta resolver ya se han resuelto y de una manera mucho mejor.

OscarRyz
fuente
1
Respuesta muy útil! GRACIAS! Entonces Hibernate se encarga de la moneda mediante un "bloqueo optimista". ¿Entonces no hay necesidad de utilizar métodos "sincronizados" para resolver cualquier concurrencia de acceso a datos? ¿Usar métodos "sincronizados" solo si los datos no están almacenados en la base de datos? ..cuando los usas ??
tomate
1
1) Creo que también hay algunos medios para usar el bloqueo pesimista. 2) No, el RDBMS puede hacer ese trabajo. 3) Si varios subprocesos acceden a los datos al mismo tiempo. 4) la sincronización es útil cuando dos hilos tienen que compartir datos. Si no lo necesitan, ¡mucho mejor!
OscarRyz
77
Cualquier restaurante de comida rápida utiliza multiproceso. Un hilo toma su orden y usa otro hilo para prepararlo, y continúa con el siguiente cliente. El punto de sincronización solo funciona cuando intercambian información para saber qué preparar. Seguir un modelo como ese realmente simplifica la vida.
OscarRyz
55
"toda la clase" no está bloqueada. La especificación del lenguaje de máquina Java : por lo 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.tanto, si un subproceso ingresa a un método estático, el mismo objeto devuelto por Object # getClass está bloqueado. Otros hilos aún pueden acceder a los métodos de instancia.
Martin Andersson
44
jajaja, me parece que mis propias palabras tampoco son tan correctas. Dije "Por lo tanto, si un hilo ingresa a un método estático, el mismo objeto devuelto por Object # getClass está bloqueado". No es técnicamente correcto. Una larga historia corta para todas las personas curiosas: para cada clase en su aplicación, existe un Classobjeto, instanciado por uno de los cargadores de clases de máquinas virtuales. Como todos los objetos, este objeto también tiene un Monitorasociado. Y este monitor es lo que se está bloqueando.
Martin Andersson el
233

Para abordar la pregunta de manera más general ...

Tenga en cuenta que usar métodos sincronizados en realidad es solo una forma abreviada (suponga que la clase es SomeClass):

synchronized static void foo() {
    ...
}

es lo mismo que

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

y

synchronized void foo() {
    ...
}

es lo mismo que

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

Puede usar cualquier objeto como cerradura. Si desea bloquear subconjuntos de métodos estáticos, puede

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 no estáticos, querrá hacer que los bloqueos sean campos no estáticos)

Scott Stanchfield
fuente
9
Esos 4 bloques de código principales son de oro. Exactamente lo que estaba buscando. Gracias.
Ryan Shillington
¿Es correcto que si uso un bloqueo estático en un método no estático, no haya dos objetos de la clase SomeClass capaces de ejecutar el bloque al mismo tiempo?
Samuel
2
@Samuel - Casi ... Se trata más de hilos que de instancias de objetos. Tienes razón en que las instancias separadas de SomeClass usarán el mismo bloqueo / monitor: el asociado con el objeto Someclass.class. Entonces, si dos hilos diferentes procesaban dos instancias diferentes de SomeClass, ambos no podrían ejecutarse al mismo tiempo. Sin embargo, si un solo hilo llamara a un método en una instancia de SomeClass, y ese método llamara a un método en la otra instancia, no ocurriría ningún bloqueo.
Scott Stanchfield
@ScottStanchfield Has enumerado formas de sincronizar métodos, ¿son todos equivalentes?
Bionix1441
1
@ Bionix1441 - Se trata de determinar el alcance. Cada mecanismo anterior le brinda un control más preciso del bloqueo. Primero, usando la instancia misma para bloquear un método completo, luego la instancia misma para bloquear secciones dentro de un método, luego cualquier instancia de objeto para bloquear secciones.
Scott Stanchfield
17

Los métodos estáticos usan la clase como el objeto para el bloqueo, que es Utils.class para su ejemplo. Entonces sí, está bien.

starblue
fuente
14

static synchronizedsignifica mantener el bloqueo en el Classobjeto de la clase donde como synchronized significa mantener el bloqueo en el objeto de esa clase. Eso significa que si está accediendo a un método sincronizado no estático en un hilo (de ejecución) aún puede acceder a un método sincronizado estático usando otro hilo.

Por lo tanto, acceder a dos métodos del mismo tipo (ya sea dos métodos estáticos o dos no estáticos) en cualquier momento por más de un hilo no es posible.

prasad
fuente
10

¿Por qué quiere exigir que solo un subproceso pueda acceder a la base de datos al mismo tiempo?

Es el trabajo del controlador de la base de datos implementar cualquier bloqueo necesario, suponiendo queConnection que solo se use un subproceso a la vez!

Lo más probable es que su base de datos sea perfectamente capaz de manejar múltiples accesos paralelos

oxbow_lakes
fuente
Apuesto a que es / fue una solución para algún problema transaccional. Es decir, la solución no resuelve el verdadero problema
matt b
1
No lo sabía ... pensé que tendría que implementar esto manualmente. ¡Gracias por mencionarlo! :)
tomate
2

Si tiene que ver con los datos de su base de datos, ¿por qué no utilizar el bloqueo de aislamiento de la base de datos para lograrlo?

Ray Lu
fuente
No tengo ningún fondo de base de datos. ¡¡Ahora sé!! ¡Gracias por mencionarlo! :)
tomate
2

Para responder a su pregunta, sí: su synchronizedmétodo no puede ser ejecutado por más de un hilo a la vez.

David Z
fuente
2

Cómo funciona la synchronizedpalabra clave Java

Cuando agrega la synchronizedpalabra clave a un método estático, el método solo puede ser llamado por un solo hilo a la vez.

En su caso, cada llamada al método:

  • crear un nuevo SessionFactory
  • crear un nuevo Session
  • buscar la entidad
  • devolver la entidad a la persona que llama

Sin embargo, estos fueron sus requisitos:

  • Quiero que esto evite el acceso a la información a la misma instancia de DB.
  • evitar que getObjectByIdse llame para todas las clases cuando una clase particular lo llama

Entonces, incluso si el getObjectByIdmétodo es seguro para subprocesos, la implementación es incorrecta.

SessionFactory mejores prácticas

El SessionFactoryes seguro para subprocesos, y es un objeto muy costoso crear, ya que necesita para analizar las clases de entidad y construir la representación interna entidad metamodelo.

Por lo tanto, no debe crear la llamada SessionFactorya cada getObjectByIdmétodo.

En su lugar, debe crear una instancia singleton para ello.

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

El Sessionsiempre debe estar cerrado

No cerró el Sessionen un finallybloque, y esto puede filtrar recursos de la base de datos si se produce una excepción al cargar la entidad.

Según el Session.loadmétodo, JavaDoc podría arrojar un HibernateExceptionsi la entidad no se puede encontrar en la base de datos.

No debe usar este método para determinar si existe una instancia (use get()en su lugar). Use esto solo para recuperar una instancia que usted supone que existe, donde la inexistencia sería un error real.

Es por eso que necesita usar un finallybloque para cerrar Session, como este:

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

Prevención de acceso multihilo

En su caso, quería asegurarse de que solo un hilo tenga acceso a esa entidad en particular.

Pero la synchronizedpalabra clave solo evita que dos hilos llamen al getObjectByIdmismo tiempo. Si los dos hilos llaman a este método uno después del otro, aún tendrá dos hilos usando esta entidad.

Entonces, si desea bloquear un objeto de base de datos dado para que ningún otro hilo pueda modificarlo, entonces necesita usar bloqueos de base de datos.

La synchronizedpalabra clave solo funciona en una sola JVM. Si tiene varios nodos web, esto no impedirá el acceso de subprocesos múltiples en varias JVM.

Lo que debe hacer es usar LockModeType.PESSIMISTIC_READoLockModeType.PESSIMISTIC_WRITE aplicar los cambios a la base de datos, de esta manera:

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

Entonces, esto es lo que hice:

  • Creé una nueva EntityTransactiony comencé una nueva transacción de base de datos
  • Cargué la Postentidad mientras mantenía un bloqueo en el registro de la base de datos asociada
  • Cambié la Postentidad y comprometí la transacción.
  • En caso de Exceptionser arrojado, revertí la transacción

Para obtener más detalles sobre ACID y las transacciones de la base de datos, consulte también este artículo .

Vlad Mihalcea
fuente