Wie funktionieren synchronisierte statische Methoden in Java und kann ich sie zum Laden von Entitäten im Ruhezustand verwenden?

177

Wenn ich eine util-Klasse mit statischen Methoden habe, die Hibernate-Funktionen aufruft, um den grundlegenden Datenzugriff zu erreichen. Ich frage mich, ob die Herstellung der Methode synchronizedder richtige Ansatz ist, um die Gewindesicherheit zu gewährleisten.

Ich möchte, dass dies den Zugriff von Informationen auf dieselbe DB-Instanz verhindert. Ich bin mir jetzt jedoch sicher, ob der folgende Code verhindert, getObjectByIddass für alle Klassen aufgerufen wird, wenn er von einer bestimmten Klasse aufgerufen wird.

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
quelle

Antworten:

136

Durch die Verwendung von synchronisiert für eine statische Methodensperre synchronisieren Sie die Klassenmethoden und -attribute (im Gegensatz zu Instanzmethoden und -attributen).

Ihre Annahme ist also richtig.

Ich frage mich, ob die Synchronisierung der Methode der richtige Ansatz ist, um die Thread-Sicherheit zu gewährleisten.

Nicht wirklich. Sie sollten dies stattdessen Ihr RDBMS ausführen lassen. Sie sind gut in solchen Sachen.

Das einzige, was Sie durch die Synchronisierung des Zugriffs auf die Datenbank erhalten, ist, Ihre Anwendung furchtbar langsam zu machen. Darüber hinaus erstellen Sie in dem von Ihnen veröffentlichten Code jedes Mal eine Session Factory. Auf diese Weise verbringt Ihre Anwendung mehr Zeit mit dem Zugriff auf die Datenbank als mit der Ausführung des eigentlichen Jobs.

Stellen Sie sich folgendes Szenario vor:

Client A und B versuchen, unterschiedliche Informationen in Datensatz X von Tabelle T einzufügen.

Mit Ihrem Ansatz müssen Sie nur sicherstellen, dass einer nach dem anderen aufgerufen wird, wenn dies in der Datenbank ohnehin passieren würde, da das RDBMS verhindert, dass sie gleichzeitig die Hälfte der Informationen von A und die Hälfte von B einfügen . Das Ergebnis ist das gleiche, aber nur fünfmal (oder mehr) langsamer.

Wahrscheinlich ist es besser, das Kapitel "Transaktionen und Parallelität" in der Dokumentation zum Ruhezustand zu lesen. In den meisten Fällen wurden die Probleme, die Sie lösen möchten, bereits und auf viel bessere Weise gelöst.

OscarRyz
quelle
1
Sehr hilfreiche Antwort! DANKE! Hibernate kümmert sich also um die Währung durch "optimistisches Sperren". Dann besteht überhaupt keine Notwendigkeit, "synchronisierte" Methoden zu verwenden, um eine Datenzugriffsübereinstimmung aufzulösen? Verwenden Sie "synchronisierte" Methoden nur, wenn die Daten nicht in der Datenbank gespeichert sind? ..wann benutzt du sie?
Tomate
1
1) Ich denke, es gibt auch einige Möglichkeiten, pessimistisches Sperren zu verwenden. 2) Nein, das RDBMS kann diese Arbeit erledigen. 3) Wenn mehrere Threads gleichzeitig auf die Daten zugreifen. 4) Die Synchronisation ist nützlich, wenn zwei Threads Daten gemeinsam nutzen müssen. Wenn sie nicht müssen, dann viel besser!
OscarRyz
7
Jedes Fast-Food-Restaurant verwendet Multithread. Ein Thread nimmt Ihre Bestellung auf und verwendet einen anderen Thread, um sie vorzubereiten, und fährt mit dem nächsten Kunden fort. Der Synchronisationspunkt funktioniert nur, wenn sie Informationen austauschen, um zu wissen, was vorzubereiten ist. Einem solchen Modell zu folgen, vereinfacht das Leben wirklich.
OscarRyz
5
"die ganze Klasse" ist nicht gesperrt. Die Java - Maschine - Sprachspezifikation : 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.Wenn also ein Thread eine statische Methode eintritt, dasselbe Objekt von zurück Objekt # getClass ist gesperrt. Andere Threads können weiterhin auf Instanzmethoden zugreifen.
Martin Andersson
4
lol Ich finde, dass meine eigene Formulierung letztendlich auch nicht so korrekt ist. Ich sagte: "Wenn also ein Thread eine statische Methode eingibt, ist dasselbe Objekt, das von Object # getClass zurückgegeben wird, gesperrt." Technisch nicht korrekt. Lange Geschichte für alle Neugierigen: Für jede Klasse in Ihrer Anwendung gibt es ein ClassObjekt, das von einem der Klassenladeprogramme für virtuelle Maschinen instanziiert wird. Wie alle Objekte ist auch diesem Objekt ein Objekt Monitorzugeordnet. Und dieser Monitor wird gesperrt.
Martin Andersson
233

Um die Frage allgemeiner zu beantworten ...

Beachten Sie, dass die Verwendung synchronisierter Methoden nur eine Abkürzung ist (vorausgesetzt, die Klasse ist SomeClass):

synchronized static void foo() {
    ...
}

ist das gleiche wie

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

und

synchronized void foo() {
    ...
}

ist das gleiche wie

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

Sie können jedes Objekt als Sperre verwenden. Wenn Sie Teilmengen statischer Methoden sperren möchten, können Sie dies tun

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

(Bei nicht statischen Methoden möchten Sie, dass die Sperren nicht statische Felder sind.)

Scott Stanchfield
quelle
9
Diese Top 4 Codeblöcke sind Gold. Genau das, wonach ich gesucht habe. Danke dir.
Ryan Shillington
Ist es richtig, dass bei Verwendung einer statischen Sperre für eine nicht statische Methode keine zwei Objekte der Klasse SomeClass gleichzeitig den Block ausführen können?
Samuel
2
@Samuel - Fast ... Es geht mehr um Threads als um Objektinstanzen. Sie haben insofern Recht, als die einzelnen Instanzen von SomeClass alle dieselbe Sperre / denselben Monitor verwenden: diejenige, die dem Someclass.class-Objekt zugeordnet ist. Wenn also zwei verschiedene Threads zwei verschiedene Instanzen von SomeClass verarbeiten, können beide nicht gleichzeitig ausgeführt werden. Wenn jedoch ein einzelner Thread in einer Instanz von SomeClass eine Methode und in der anderen Instanz eine Methode aufruft, erfolgt keine Blockierung.
Scott Stanchfield
@ScottStanchfield Sie haben Möglichkeiten zum Synchronisieren von Methoden aufgelistet. Sind alle gleichwertig?
Bionix1441
1
@ Bionix1441 - Es geht nur um Scoping. Mit jedem der oben genannten Mechanismen können Sie die Verriegelung genauer steuern. Verwenden Sie zuerst die Instanz selbst, um eine gesamte Methode zu sperren, dann die Instanz selbst, um Abschnitte innerhalb einer Methode zu sperren, und dann jede Objektinstanz, um Abschnitte zu sperren.
Scott Stanchfield
17

Statische Methoden verwenden die Klasse als Objekt zum Sperren. In Ihrem Beispiel ist dies Utils.class. Also ja, es ist OK.

Sternenblau
quelle
14

static synchronizedbedeutet, das ClassObjekt der Klasse zu synchronizedsperren, wobei das Objekt dieser Klasse selbst gesperrt bleibt. Das heißt, wenn Sie in einem Thread (der Ausführung) auf eine nicht statisch synchronisierte Methode zugreifen, können Sie weiterhin mit einem anderen Thread auf eine statisch synchronisierte Methode zugreifen.

Daher ist es zu keinem Zeitpunkt mehr als einem Thread möglich, auf zwei gleiche Arten von Methoden (entweder zwei statische oder zwei nicht statische Methoden) zuzugreifen.

Prasad
quelle
10

Warum möchten Sie erzwingen, dass jeweils nur ein Thread auf die Datenbank zugreifen kann?

Es ist die Aufgabe des Datenbanktreibers , alle erforderlichen Sperren zu implementieren, vorausgesetzt, a Connectionwird jeweils nur von einem Thread verwendet!

Höchstwahrscheinlich kann Ihre Datenbank mehrere parallele Zugriffe ausführen

oxbow_lakes
quelle
Ich wette, es ist / war eine Problemumgehung für ein Transaktionsproblem. Dh die Lösung löst nicht das wahre Problem
matt b
1
Ich wusste das nicht ... Ich dachte, ich müsste das manuell implementieren. Vielen Dank für den Hinweis! :)
Tomate
2

Wenn dies mit den Daten in Ihrer Datenbank zu tun hat, können Sie die Sperre der Datenbankisolation verwenden, um dies zu erreichen.

Ray Lu
quelle
Ich habe keinen Datenbankhintergrund. Jetzt weiß ich!! Vielen Dank für den Hinweis! :)
Tomate
2

Ja, um Ihre Frage zu beantworten: Ihre synchronizedMethode kann nicht von mehr als einem Thread gleichzeitig ausgeführt werden.

David Z.
quelle
2

Wie das synchronizedJava-Schlüsselwort funktioniert

Wenn Sie das synchronizedSchlüsselwort zu einer statischen Methode hinzufügen , kann die Methode jeweils nur von einem einzelnen Thread aufgerufen werden.

In Ihrem Fall wird jeder Methodenaufruf:

  • erstelle eine neue SessionFactory
  • erstelle eine neue Session
  • Holen Sie sich die Entität
  • Geben Sie die Entität an den Anrufer zurück

Dies waren jedoch Ihre Anforderungen:

  • Ich möchte, dass dies den Zugriff auf Informationen zu derselben DB-Instanz verhindert.
  • Verhindern, getObjectByIddass für alle Klassen aufgerufen wird, wenn es von einer bestimmten Klasse aufgerufen wird

Selbst wenn die getObjectByIdMethode threadsicher ist, ist die Implementierung falsch.

SessionFactory empfohlene Vorgehensweise

Das SessionFactoryist threadsicher und es ist ein sehr teures Objekt, das erstellt werden muss, da die Entitätsklassen analysiert und die interne Entitätsmetamodelldarstellung erstellt werden müssen.

Sie sollten also nicht SessionFactorybei jedem getObjectByIdMethodenaufruf das erstellen .

Stattdessen sollten Sie eine Singleton-Instanz dafür erstellen.

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

Das Sessionsollte immer geschlossen sein

Sie haben das Sessionin einem finallyBlock nicht geschlossen, und dies kann zu Datenbankressourcen führen, wenn beim Laden der Entität eine Ausnahme ausgelöst wird.

Gemäß der Session.loadMethode kann JavaDoc ein auslösen , HibernateExceptionwenn die Entität nicht in der Datenbank gefunden werden kann.

Sie sollten diese Methode nicht verwenden, um festzustellen, ob eine Instanz vorhanden ist ( get()stattdessen verwenden). Verwenden Sie diese Option nur, um eine Instanz abzurufen, von der Sie annehmen, dass sie existiert, wobei Nichtexistenz ein tatsächlicher Fehler wäre.

Aus diesem Grund müssen Sie einen finallyBlock verwenden, um das zu schließen Session:

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

Multithread-Zugriff verhindern

In Ihrem Fall wollten Sie sicherstellen, dass nur ein Thread Zugriff auf diese bestimmte Entität erhält.

Das synchronizedSchlüsselwort verhindert jedoch nur, dass zwei Threads getObjectByIdgleichzeitig aufrufen . Wenn die beiden Threads diese Methode nacheinander aufrufen, haben Sie immer noch zwei Threads, die diese Entität verwenden.

Wenn Sie also ein bestimmtes Datenbankobjekt sperren möchten, damit kein anderer Thread es ändern kann, müssen Sie Datenbanksperren verwenden.

Das synchronizedSchlüsselwort funktioniert nur in einer einzelnen JVM. Wenn Sie mehrere Webknoten haben, wird dadurch der Multithread-Zugriff über mehrere JVMs nicht verhindert.

Sie müssen die Änderungen wie folgt verwenden LockModeType.PESSIMISTIC_READoderLockModeType.PESSIMISTIC_WRITE anwenden:

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

Also, das habe ich getan:

  • Ich habe eine neue erstellt EntityTransactionund eine neue Datenbanktransaktion gestartet
  • Ich habe die PostEntität geladen , während ich den zugehörigen Datenbankeintrag gesperrt habe
  • Ich habe die PostEntität geändert und die Transaktion festgeschrieben
  • Im Falle eines ExceptionWerfens habe ich die Transaktion zurückgesetzt

Weitere Informationen zu ACID- und Datenbanktransaktionen finden Sie auch in diesem Artikel .

Vlad Mihalcea
quelle