Jak działają zsynchronizowane metody statyczne w Javie i czy mogę ich używać do ładowania jednostek Hibernate?

179

Jeśli mam klasę util ze statycznymi metodami, które będą wywoływać funkcje Hibernate w celu uzyskania podstawowego dostępu do danych. Zastanawiam się, czy wykonanie tej metody synchronizedjest właściwym podejściem do zapewnienia bezpieczeństwa nici.

Chcę, aby to uniemożliwiło dostęp do informacji do tej samej instancji bazy danych. Jednak jestem teraz pewien, czy poniższy kod uniemożliwia getObjectByIdwywołanie wszystkich klas, gdy jest wywoływany przez określoną klasę.

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
}
pomidor
źródło

Odpowiedzi:

136

Używając zsynchronizowanych na statycznej blokadzie metod, zsynchronizujesz metody i atrybuty klas (w przeciwieństwie do metod i atrybutów instancji)

Więc twoje założenie jest poprawne.

Zastanawiam się, czy zsynchronizowanie metody jest właściwym podejściem do zapewnienia bezpieczeństwa wątków.

Nie całkiem. Zamiast tego powinieneś pozwolić RDBMS na wykonanie tej pracy. Są dobrzy w tego typu rzeczach.

Jedyne, co uzyskasz synchronizując dostęp do bazy danych, to bardzo spowolnienie aplikacji. Co więcej, w kodzie, który opublikowałeś, za każdym razem budujesz fabrykę sesji, dzięki czemu Twoja aplikacja będzie spędzać więcej czasu na uzyskiwaniu dostępu do bazy danych niż na wykonywaniu rzeczywistej pracy.

Wyobraź sobie następujący scenariusz:

Klient A i B próbują wstawić różne informacje do rekordu X tabeli T.

Przy twoim podejściu jedyną rzeczą, którą otrzymujesz, jest upewnienie się, że jedna jest wywoływana po drugiej, kiedy i tak by się to zdarzyło w DB, ponieważ RDBMS uniemożliwi im wstawianie połowy informacji z A i połowy z B w tym samym czasie . Wynik będzie taki sam, ale tylko 5 razy (lub więcej) wolniejszy.

Prawdopodobnie lepiej byłoby przyjrzeć się rozdziałowi „Transakcje i współbieżność” w dokumentacji Hibernate. W większości przypadków problemy, które próbujesz rozwiązać, zostały już rozwiązane i to w znacznie lepszy sposób.

OscarRyz
źródło
1
Bardzo pomocna odpowiedź! DZIĘKI! Zatem Hibernate dba o walutę poprzez „optymistyczne blokowanie”. Wtedy nie ma potrzeby używania „zsynchronizowanych” metod w celu rozwiązania problemu współbieżności dostępu do danych? Używaj metod „zsynchronizowanych” tylko wtedy, gdy dane nie są przechowywane w bazie danych? ..Kiedy ich używasz?
pomidor
1
1) Myślę, że są też sposoby na wykorzystanie pesymistycznego blokowania. 2) Nie, RDBMS może wykonać tę pracę. 3) Jeśli dane są dostępne w wielu wątkach w tym samym czasie. 4) synchronizacja jest przydatna, gdy dwa wątki muszą udostępniać dane. Jeśli nie muszą, to znacznie lepiej!
OscarRyz
7
Każda restauracja typu fast food używa wielowątkowości. Jeden wątek przyjmuje zamówienie i używa drugiego do przygotowania, a następnie przechodzi do następnego klienta. Punkt synchronizacji działa tylko wtedy, gdy wymieniają się informacjami, aby wiedzieć, co należy przygotować. Podążanie za takim modelem naprawdę upraszcza życie.
OscarRyz,
5
„cała klasa” nie jest zablokowana. Specyfikacja języka maszynowego 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.Tak więc, jeśli jeden wątek przechodzi do metody statycznej, ten sam obiekt zwracany przez Object # getClass jest blokowany. Inne wątki nadal mogą uzyskiwać dostęp do metod instancji.
Martin Andersson
4
lol Uważam, że moje własne sformułowanie też nie jest ostatecznie poprawne. Powiedziałem: „Zatem jeśli jeden wątek wejdzie w metodę statyczną, ten sam obiekt zwrócony przez Object # getClass jest zablokowany”. Nieprawidłowe technicznie. Krótka historia dla wszystkich ciekawskich ludzi: dla każdej klasy w Twojej aplikacji istnieje Classobiekt, którego instancja jest tworzona przez jeden z programów ładujących klasy maszyn wirtualnych. Podobnie jak wszystkie obiekty, ten obiekt również ma Monitorz nim skojarzony. A ten monitor jest tym, co jest blokowane.
Martin Andersson
233

Aby odpowiedzieć na pytanie bardziej ogólnie ...

Pamiętaj, że używanie zsynchronizowanych metod jest naprawdę skrótowe (zakładając, że klasa to SomeClass):

synchronized static void foo() {
    ...
}

jest taki sam jak

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

i

synchronized void foo() {
    ...
}

jest taki sam jak

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

Możesz użyć dowolnego przedmiotu jako zamka. Jeśli chcesz zablokować podzbiory metod statycznych, możesz to zrobić

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

(w przypadku metod niestatycznych chciałbyś, aby blokady były polami niestatycznymi)

Scott Stanchfield
źródło
9
Te 4 główne bloki kodu są złote. Dokładnie to, czego szukałem. Dziękuję Ci.
Ryan Shillington
Czy to prawda, że ​​jeśli użyję statycznej blokady na metodzie niestatycznej, żadne dwa obiekty klasy SomeClass nie będą w stanie uruchomić bloku w tym samym czasie?
Samuel
2
@Samuel - prawie ... Chodzi bardziej o wątki niż o instancje obiektów. Masz rację, ponieważ oddzielne instancje SomeClass będą używać tej samej blokady / monitora: tej skojarzonej z obiektem Someclass.class. Więc jeśli dwa różne wątki przetwarzałyby dwa różne wystąpienia SomeClass, oba nie mogły działać w tym samym czasie. Jeśli jednak pojedynczy wątek wywoła metodę w jednym wystąpieniu SomeClass, a ta metoda w drugim wystąpieniu, nie nastąpi blokowanie.
Scott Stanchfield
@ScottStanchfield Podałeś sposoby synchronizacji metod, czy wszystkie są równoważne?
Bionix1441
1
@ Bionix1441 - Chodzi o zakres. Każdy mechanizm powyżej zapewnia lepszą kontrolę nad blokadą. Najpierw użycie samej instancji do zablokowania całej metody, następnie samej instancji do zablokowania sekcji wewnątrz metody, a następnie dowolnej instancji obiektu do zablokowania sekcji.
Scott Stanchfield,
17

Metody statyczne używają klasy jako obiektu do blokowania, którym na przykład jest Utils.class. Więc tak, jest OK.

starblue
źródło
14

static synchronizedoznacza utrzymywanie blokady na obiekcie klasy, Classgdzie jako synchronizedoznacza utrzymywanie blokady na samym obiekcie tej klasy. Oznacza to, że jeśli uzyskujesz dostęp do niestatycznej metody synchronizowanej w wątku (wykonywania), nadal możesz uzyskać dostęp do statycznej metody synchronizowanej przy użyciu innego wątku.

Zatem dostęp do dwóch metod tego samego rodzaju (albo dwóch statycznych albo dwóch niestatycznych) w dowolnym momencie przez więcej niż jeden wątek nie jest możliwy.

prasad
źródło
10

Dlaczego chcesz wymusić, że tylko jeden wątek może uzyskać dostęp do bazy danych w dowolnym momencie?

Zadaniem sterownika bazy danych jest zaimplementowanie wszelkich niezbędnych blokad, zakładając, że Connectionw danym momencie a jest używany tylko przez jeden wątek!

Najprawdopodobniej Twoja baza danych doskonale radzi sobie z wielokrotnym dostępem równoległym

oxbow_lakes
źródło
Założę się, że jest to obejście problemu związanego z transakcjami. To znaczy, rozwiązanie nie rozwiązuje prawdziwego problemu
Matt b
1
Nie wiedziałem tego… Pomyślałem, że będę musiał to zaimplementować ręcznie. Dzięki za zwrócenie uwagi! :)
pomidor
2

Jeśli ma to coś wspólnego z danymi w Twojej bazie danych, dlaczego nie wykorzystać do tego blokowania izolacji bazy danych?

Ray Lu
źródło
Nie mam żadnego tła bazy danych. Teraz wiem!! Dzięki za zwrócenie uwagi! :)
pomidor
2

Odpowiadając na twoje pytanie, tak: twoja synchronizedmetoda nie może być wykonywana jednocześnie przez więcej niż jeden wątek.

David Z
źródło
2

Jak synchronizeddziała słowo kluczowe Java

Po dodaniu synchronizedsłowa kluczowego do metody statycznej metoda może być wywoływana jednocześnie tylko przez jeden wątek.

W twoim przypadku każde wywołanie metody:

  • Stwórz nowy SessionFactory
  • Stwórz nowy Session
  • pobierz jednostkę
  • zwraca jednostkę z powrotem do dzwoniącego

Jednak takie były Twoje wymagania:

  • Chcę, aby to uniemożliwiło dostęp do informacji do tej samej instancji bazy danych.
  • zapobieganie getObjectByIdwywoływaniu wszystkich klas, gdy jest wywoływane przez określoną klasę

Tak więc, nawet jeśli getObjectByIdmetoda jest bezpieczna dla wątków, implementacja jest nieprawidłowa.

SessionFactory najlepsze praktyki

SessionFactoryJest thread-safe, i jest to bardzo kosztowny obiekt do tworzenia, ile potrzebuje do analizowania klas encji i budować wewnętrzną reprezentację podmiotu metamodel.

Dlatego nie powinieneś tworzyć wywołania metody SessionFactoryprzy każdym getObjectByIdwywołaniu.

Zamiast tego należy utworzyć dla niego pojedynczą instancję.

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

SessionPowinny być zawsze zamknięte

Nie zamknąłeś bloku Sessionw finallybloku, co może spowodować wyciek zasobów bazy danych, jeśli podczas ładowania jednostki zostanie zgłoszony wyjątek.

Zgodnie z Session.loadmetodą JavaDoc może HibernateExceptionzgłosić, jeśli jednostka nie może zostać znaleziona w bazie danych.

Nie należy używać tej metody do określania, czy instancja istnieje ( get()zamiast tego użyj ). Użyj tego tylko do pobrania instancji, która, jak zakładasz, istnieje, a nieistnienie byłoby rzeczywistym błędem.

Dlatego musisz użyć finallybloku, aby zamknąć Session, w ten sposób:

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

Zapobieganie dostępowi do wielu wątków

W twoim przypadku chciałeś mieć pewność, że tylko jeden wątek uzyska dostęp do tej konkretnej jednostki.

Ale synchronizedsłowo kluczowe zapobiega tylko getObjectByIdrównoczesnemu wywoływaniu dwóch wątków . Jeśli dwa wątki wywołują tę metodę jeden po drugim, nadal będziesz mieć dwa wątki korzystające z tej jednostki.

Tak więc, jeśli chcesz zablokować dany obiekt bazy danych, aby żaden inny wątek nie mógł go zmodyfikować, musisz użyć blokad bazy danych.

Słowo synchronizedkluczowe działa tylko w pojedynczej maszynie JVM. Jeśli masz wiele węzłów WWW, nie zapobiegnie to wielowątkowemu dostępowi do wielu maszyn JVM.

To, co musisz zrobić, to użyć LockModeType.PESSIMISTIC_READlubLockModeType.PESSIMISTIC_WRITE podczas stosowania zmian w bazie danych, na przykład:

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

Tak więc zrobiłem:

  • Utworzyłem nową EntityTransactioni rozpocząłem nową transakcję w bazie danych
  • Załadowałem Postjednostkę, trzymając blokadę skojarzonego rekordu bazy danych
  • Zmieniłem Postpodmiot i dokonałem transakcji
  • W przypadku Exceptionwyrzucenia wycofałem transakcję

Więcej informacji na temat ACID i transakcji bazodanowych można znaleźć w tym artykule .

Vlad Mihalcea
źródło