Come funzionano i metodi statici sincronizzati in Java e posso usarlo per caricare entità Hibernate?


179

Se ho una classe util con metodi statici che chiamerà le funzioni di ibernazione per ottenere l'accesso ai dati di base. Mi chiedo se realizzare il metodo synchronizedsia l'approccio giusto per garantire la sicurezza del thread.

Voglio che ciò impedisca l'accesso delle informazioni alla stessa istanza DB. Tuttavia, ora sono sicuro che il codice seguente impedisce di getObjectByIdessere chiamato per tutte le classi quando viene chiamato da una determinata classe.

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
}

Risposte:


136

Usando sincronizzato su un blocco di metodo statico sincronizzerete i metodi e gli attributi della classe (al contrario dei metodi e degli attributi dell'istanza)

Quindi il tuo presupposto è corretto.

Mi chiedo se rendere il metodo sincronizzato sia l'approccio giusto per garantire la sicurezza dei thread.

Non proprio. Dovresti invece lasciare che il tuo RDBMS funzioni correttamente. Sono bravi in ​​questo tipo di cose.

L'unica cosa che otterrai sincronizzando l'accesso al database è rendere l'applicazione estremamente lenta. Inoltre, nel codice che hai pubblicato stai costruendo una Session Factory ogni volta, in questo modo la tua applicazione impiegherà più tempo ad accedere al DB che a eseguire il lavoro effettivo.

Immagina il seguente scenario:

I client A e B tentano di inserire informazioni diverse nel record X della tabella T.

Con il tuo approccio l'unica cosa che stai ottenendo è assicurarti che uno venga chiamato dopo l'altro, quando ciò accadrà comunque nel DB, perché l'RDBMS impedirà loro di inserire metà informazioni da A e metà da B contemporaneamente . Il risultato sarà lo stesso ma solo 5 volte (o più) più lentamente.

Probabilmente potrebbe essere meglio dare un'occhiata al capitolo "Transazioni e concorrenza" nella documentazione di Hibernate. Il più delle volte i problemi che stai cercando di risolvere sono già stati risolti e un modo molto migliore.


1
Risposta molto utile! GRAZIE! Quindi Hibernate si occupa della cnocurrency mediante "blocco ottimistico". Quindi non è necessario utilizzare metodi "sincronizzati" per risolvere eventuali problemi di accesso ai dati ?? Utilizzare metodi "sincronizzati" solo se i dati non sono memorizzati nel database ?? ..quando li usi ??
pomodoro,

1
1) Penso che ci siano alcuni modi per usare anche il blocco pessimistico. 2) No, l'RDBMS può farlo. 3) Se si accede ai dati da più thread contemporaneamente. 4) la sincronizzazione è utile quando due thread devono condividere dati. Se non ne hanno bisogno, molto meglio!
OscarRyz,

7
Qualsiasi ristorante fast food utilizza il multithread. Un thread prende l'ordine e usa un altro thread per prepararlo e continua con il cliente successivo. Il punto di sincronizzazione funziona solo quando scambiano informazioni per sapere cosa preparare. Seguire un modello del genere semplifica davvero la vita.
OscarRyz,

5
"l'intera classe" non è bloccata. La specifica del linguaggio macchina 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.Pertanto se un thread entra in un metodo statico, lo stesso oggetto restituito da Object # getClass è bloccato. Altri thread possono ancora accedere ai metodi di istanza.
Martin Andersson,

4
lol Trovo che le mie stesse parole non siano neanche alla fine corrette. Ho detto "Quindi se un thread entra in un metodo statico, lo stesso oggetto restituito da Object # getClass è bloccato". Tecnicamente non corretto. Per farla breve, per tutte le persone curiose: per ogni classe dell'applicazione, esiste un Classoggetto, istanziato da uno dei classloader di macchine virtuali. Come tutti gli oggetti, anche questo oggetto è Monitorassociato. E questo monitor è ciò che viene bloccato.
Martin Andersson,

233

Per rispondere alla domanda più in generale ...

Tieni presente che l'utilizzo dei metodi sincronizzati è davvero una scorciatoia (supponiamo che la classe sia SomeClass):

synchronized static void foo() {
    ...
}

equivale a

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

e

synchronized void foo() {
    ...
}

equivale a

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

È possibile utilizzare qualsiasi oggetto come blocco. Se si desidera bloccare sottoinsiemi di metodi statici, è possibile

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) {...}
    }
}

(per metodi non statici, si desidera rendere i blocchi non campi statici)


9
Quei primi 4 blocchi di codice sono oro. Esattamente quello che stavo cercando. Grazie.
Ryan Shillington,

È corretto che se uso un Lock statico su un metodo non statico, nessun oggetto della classe SomeClass sarà in grado di eseguire il blocco contemporaneamente?
Samuel,

2
@ Samuel - Quasi ... Si tratta più di thread che di istanze di oggetti. Hai ragione in quanto le istanze separate di SomeClass useranno tutte lo stesso lock / monitor: quello associato all'oggetto Someclass.class. Quindi, se due thread diversi stavano elaborando due diverse istanze di SomeClass, entrambi non potevano essere eseguiti contemporaneamente. Tuttavia, se un singolo thread chiamasse un metodo in un'istanza di SomeClass e quel metodo chiamasse un metodo nell'altra istanza, non si verificherebbe alcun blocco.
Scott Stanchfield,

@ScottStanchfield Hai elencato i modi per sincronizzare i metodi, sono tutti equivalenti?
Bionix1441,

1
@ Bionix1441 - Si tratta solo di scoping. Ogni meccanismo sopra ti dà un controllo più preciso del bloccaggio. Innanzitutto, utilizzando l'istanza stessa bloccando un intero metodo, quindi l'istanza stessa per bloccare le sezioni all'interno di un metodo, quindi qualsiasi istanza di oggetto per bloccare le sezioni.
Scott Stanchfield,

17

I metodi statici utilizzano la classe come oggetto per il blocco, che è Utils.class per il tuo esempio. Quindi sì, va bene.


14

static synchronizedsignifica tenere bloccato l' Classoggetto della classe dove as synchronized significa trattenere il blocco sull'oggetto della classe stessa. Ciò significa che se si accede a un metodo sincronizzato non statico in un thread (di esecuzione) è comunque possibile accedere a un metodo sincronizzato statico utilizzando un altro thread.

Pertanto, non è possibile accedere a due metodi dello stesso tipo (due metodi statici o due non statici) in qualsiasi momento per più di un thread.


10

Perché vuoi imporre che solo un singolo thread possa accedere al DB alla volta?

È compito del driver del database implementare qualsiasi blocco necessario, supponendo che a Connectionsia usato solo da un thread alla volta!

Molto probabilmente, il tuo database è perfettamente in grado di gestire più accessi paralleli


Scommetto che è / è stata una soluzione alternativa per qualche problema di transazione. Vale a dire, la soluzione non risolve il vero problema
matt b

1
Non lo sapevo .... Pensavo che avrei dovuto implementarlo manualmente. Grazie per segnalarlo! :)
pomodoro

2

Se ha a che fare con i dati nel database, perché non utilizzare il blocco dell'isolamento del database per ottenere?


Non ho alcun background di database. Adesso lo so!! Grazie per segnalarlo! :)
pomodoro

2

Per rispondere alla tua domanda, sì, sì: il tuo synchronizedmetodo non può essere eseguito da più di un thread alla volta.


2

Come funziona la synchronizedparola chiave Java

Quando si aggiunge la synchronizedparola chiave a un metodo statico, il metodo può essere chiamato solo da un singolo thread alla volta.

Nel tuo caso, ogni chiamata di metodo sarà:

  • creane uno nuovo SessionFactory
  • creane uno nuovo Session
  • recuperare l'entità
  • restituisce l'entità al chiamante

Tuttavia, questi erano i tuoi requisiti:

  • Voglio che ciò impedisca l'accesso alle informazioni alla stessa istanza DB.
  • impedendo di getObjectByIdessere chiamato per tutte le classi quando viene chiamato da una determinata classe

Pertanto, anche se il getObjectByIdmetodo è thread-safe, l'implementazione è errata.

SessionFactory migliori pratiche

Il SessionFactory è thread-safe, ed è un oggetto molto costoso creare poiché è necessario analizzare le classi di entità e costruire la rappresentazione interna entità metamodel.

Quindi, non dovresti creare il SessionFactoryon ognigetObjectById chiamata metodo.

Invece, è necessario creare un'istanza singleton per esso.

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

Il Session devono essere chiusi

Non hai chiuso Sessionin afinally blocco e questo può perdere le risorse del database se viene generata un'eccezione durante il caricamento dell'entità.

Secondo il Session.loadmetodo JavaDoc potrebbe generare un erroreHibernateException se l'entità non è stata trovata nel database.

Non utilizzare questo metodo per determinare se esiste un'istanza (utilizzare get()invece). Utilizzalo solo per recuperare un'istanza che ritieni esista, in cui la non esistenza sarebbe un errore reale.

Ecco perché è necessario utilizzare un finallyblocco per chiudere Session, in questo modo:

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

Prevenire l'accesso multi-thread

Nel tuo caso, volevi assicurarti che solo un thread potesse accedere a quella particolare entità.

Ma la synchronizedparola chiave impedisce solo a due thread di chiamare getObjectByIdcontemporaneamente. Se i due thread chiamano questo metodo uno dopo l'altro, avrai comunque due thread che utilizzano questa entità.

Pertanto, se si desidera bloccare un determinato oggetto di database in modo che nessun altro thread possa modificarlo, è necessario utilizzare i blocchi di database.

La synchronizedparola chiave funziona solo in una singola JVM. Se si dispone di più nodi Web, ciò non impedirà l'accesso multi-thread su più JVM.

Quello che devi fare è usare LockModeType.PESSIMISTIC_READoLockModeType.PESSIMISTIC_WRITE mentre applichi le modifiche al DB, in questo modo:

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

Quindi, questo è quello che ho fatto:

  • Ho creato un nuovo EntityTransactione avviato una nuova transazione di database
  • Ho caricato l' Postentità tenendo un blocco sul record del database associato
  • Ho cambiato l' Postentità e ho eseguito la transazione
  • Nel caso di un Exceptionlancio, ho eseguito il rollback della transazione

Per maggiori dettagli sulle transazioni ACID e database, consulta anche questo articolo .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.