Comment fonctionnent les méthodes statiques synchronisées en Java et puis-je l'utiliser pour charger des entités Hibernate?

179

Si j'ai une classe util avec des méthodes statiques qui appelleront les fonctions Hibernate pour accéder aux données de base. Je me demande si l'élaboration de la méthode synchronizedest la bonne approche pour garantir la sécurité des threads.

Je veux que cela empêche l'accès des informations à la même instance de base de données. Cependant, je suis maintenant sûr que le code suivant empêche d' getObjectByIdêtre appelé pour toutes les classes lorsqu'il est appelé par une classe particulière.

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
la source

Réponses:

136

En utilisant synchronized sur un verrou de méthode statique, vous synchroniserez les méthodes et attributs de classe (par opposition aux méthodes et attributs d'instance)

Votre hypothèse est donc correcte.

Je me demande si la synchronisation de la méthode est la bonne approche pour garantir la sécurité des threads.

Pas vraiment. Vous devriez plutôt laisser votre SGBDR faire ce travail. Ils sont bons dans ce genre de choses.

La seule chose que vous obtiendrez en synchronisant l'accès à la base de données est de rendre votre application terriblement lente. De plus, dans le code que vous avez publié, vous créez une usine de session à chaque fois, de cette façon, votre application passera plus de temps à accéder à la base de données qu'à effectuer le travail réel.

Imaginez le scénario suivant:

Les clients A et B tentent d'insérer des informations différentes dans l'enregistrement X du tableau T.

Avec votre approche, la seule chose que vous obtenez est de vous assurer que l'un est appelé après l'autre, quand cela se produirait de toute façon dans la base de données, car le SGBDR les empêchera d'insérer la moitié des informations de A et la moitié de B en même temps . Le résultat sera le même mais seulement 5 fois (ou plus) plus lentement.

Il serait probablement préférable de jeter un œil au chapitre "Transactions et concurrence" dans la documentation Hibernate. La plupart du temps, les problèmes que vous essayez de résoudre ont déjà été résolus et d'une bien meilleure façon.

OscarRyz
la source
1
Réponse très utile! MERCI! Donc Hibernate s'occupe de la cnocurrency par "verrouillage optimiste". Alors il n'y a aucun besoin d'utiliser des méthodes "synchronisées" pour résoudre n'importe quelle concurrecy d'accès aux données ?? N'utilisez les méthodes "synchronisées" que si les données ne sont pas stockées dans la base de données ?? ..Quand les utilisez-vous ??
tomate
1
1) Je pense qu'il existe également des moyens d'utiliser le verrouillage pessimiste. 2) Non, le SGBDR peut faire ce travail. 3) Si les données sont accessibles par plusieurs threads en même temps. 4) la synchronisation est utile lorsque deux threads doivent partager des données. S'ils n'en ont pas besoin, tant mieux!
OscarRyz
7
Tout restaurant de restauration rapide utilise le multithread. Un thread prend votre commande et utilise un autre thread pour le préparer, et continue avec le client suivant. Le point de synchronisation ne fonctionne que lorsqu'ils échangent des informations pour savoir quoi préparer. Suivre un modèle comme celui-là simplifie vraiment la vie.
OscarRyz
5
"toute la classe" n'est pas verrouillée. La spécification du langage machine 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.Ainsi, si un thread entre dans une méthode statique, le même objet renvoyé par Object # getClass est verrouillé. D'autres threads peuvent toujours accéder aux méthodes d'instance.
Martin Andersson
4
lol je trouve que ma propre formulation n'est finalement pas correcte non plus. J'ai dit "Ainsi si un thread entre dans une méthode statique, le même objet retourné par Object # getClass est verrouillé". Pas techniquement correct. Longue histoire en bref pour tous les curieux: pour chaque classe de votre application, il existe un Classobjet, instancié par l'un des chargeurs de classe des machines virtuelles. Comme tous les objets, cet objet a également un Monitorassocié. Et ce moniteur est ce qui est verrouillé.
Martin Andersson
233

Pour aborder la question plus généralement ...

Gardez à l'esprit que l'utilisation de méthodes synchronisées n'est en réalité qu'un raccourci (supposons que la classe est SomeClass):

synchronized static void foo() {
    ...
}

est le même que

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

et

synchronized void foo() {
    ...
}

est le même que

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

Vous pouvez utiliser n'importe quel objet comme verrou. Si vous souhaitez verrouiller des sous-ensembles de méthodes statiques, vous pouvez

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

(pour les méthodes non statiques, vous voudriez que les verrous soient des champs non statiques)

Scott Stanchfield
la source
9
Ces 4 blocs de code principaux sont en or. Exactement ce que je cherchais. Je vous remercie.
Ryan Shillington le
Est-il correct que si j'utilise un verrou statique sur une méthode non statique, deux objets de la classe SomeClass ne pourront pas exécuter le bloc en même temps?
Samuel
2
@Samuel - Presque ... Il s'agit plus de threads que d'instances d'objets. Vous avez raison en ce que les instances séparées de SomeClass utiliseront toutes le même verrou / moniteur: celui associé à l'objet Someclass.class. Donc, si deux threads différents traitaient deux instances différentes de SomeClass, ils ne pouvaient pas tous les deux s'exécuter en même temps. Cependant, si un seul thread appelait une méthode dans une instance de SomeClass et que cette méthode appelait une méthode dans l'autre instance, aucun blocage ne se produirait.
Scott Stanchfield
@ScottStanchfield Vous avez répertorié des méthodes de synchronisation des méthodes, sont-elles toutes équivalentes?
Bionix1441
1
@ Bionix1441 - Tout est question de portée. Chaque mécanisme ci-dessus vous donne un contrôle plus fin du verrouillage. Tout d'abord, en utilisant l'instance elle-même pour verrouiller une méthode entière, puis l'instance elle-même pour verrouiller les sections à l'intérieur d'une méthode, puis toute instance d'objet pour verrouiller les sections.
Scott Stanchfield
17

Les méthodes statiques utilisent la classe comme objet de verrouillage, qui est Utils.class pour votre exemple. Alors oui, ça va.

starblue
la source
14

static synchronizedsignifie maintenir le verrou sur l' Classobjet de la classe alors que cela synchronizedsignifie maintenir le verrou sur l'objet de cette classe lui-même. Cela signifie que si vous accédez à une méthode synchronisée non statique dans un thread (d'exécution), vous pouvez toujours accéder à une méthode synchronisée statique à l'aide d'un autre thread.

Ainsi, accéder à deux méthodes du même type (soit deux méthodes statiques soit deux méthodes non statiques) à tout moment par plus d'un thread n'est pas possible.

prasad
la source
10

Pourquoi voulez-vous imposer qu'un seul thread puisse accéder à la base de données à la fois?

C'est le travail du pilote de base de données d'implémenter tout verrouillage nécessaire, en supposant que a Connectionn'est utilisé que par un thread à la fois!

Très probablement, votre base de données est parfaitement capable de gérer plusieurs accès parallèles

oxbow_lakes
la source
Je parie que c'est / était une solution de contournement pour un problème transactionnel. Ie, la solution ne résout pas le vrai problème
matt b
1
Je ne savais pas que ... Je pensais que je devrais mettre en œuvre cela manuellement. Merci de l'avoir signalé! :)
tomate
2

S'il s'agit de quelque chose à voir avec les données de votre base de données, pourquoi ne pas utiliser le verrouillage d'isolation de base de données pour y parvenir?

Ray Lu
la source
Je n'ai aucune base de données. Maintenant je sais!! Merci de l'avoir signalé! :)
tomate
2

Pour répondre à votre question, oui: votre synchronizedméthode ne peut pas être exécutée par plus d'un thread à la fois.

David Z
la source
2

Fonctionnement du synchronizedmot-clé Java

Lorsque vous ajoutez le synchronizedmot - clé à une méthode statique, la méthode ne peut être appelée que par un seul thread à la fois.

Dans votre cas, chaque appel de méthode:

  • créer un nouveau SessionFactory
  • créer un nouveau Session
  • chercher l'entité
  • renvoyer l'entité à l'appelant

Cependant, voici vos exigences:

  • Je veux que cela empêche l'accès aux informations de la même instance de base de données.
  • éviter d' getObjectByIdêtre appelé pour toutes les classes lorsqu'il est appelé par une classe particulière

Ainsi, même si la getObjectByIdméthode est thread-safe, l'implémentation est incorrecte.

SessionFactory les meilleures pratiques

Le SessionFactoryest thread-safe, et c'est un objet très coûteux à créer car il doit analyser les classes d'entité et construire la représentation du métamodèle d'entité interne.

Donc, vous ne devriez pas créer le SessionFactoryà chaque getObjectByIdappel de méthode.

Au lieu de cela, vous devez créer une instance singleton pour elle.

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

Le Sessiondevrait toujours être fermé

Vous n'avez pas fermé le Sessiondans un finallybloc, ce qui peut entraîner une fuite des ressources de la base de données si une exception est levée lors du chargement de l'entité.

Selon la Session.loadméthode, JavaDoc peut lancer un HibernateExceptionsi l'entité est introuvable dans la base de données.

Vous ne devez pas utiliser cette méthode pour déterminer si une instance existe (utilisez à la get()place). Utilisez ceci uniquement pour récupérer une instance dont vous supposez qu'elle existe, où la non-existence serait une erreur réelle.

C'est pourquoi vous devez utiliser un finallybloc pour fermer le Session, comme ceci:

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

Empêcher l'accès multi-thread

Dans votre cas, vous vouliez vous assurer qu'un seul thread ait accès à cette entité particulière.

Mais le synchronizedmot - clé empêche seulement deux threads d'appeler getObjectByIdsimultanément. Si les deux threads appellent cette méthode l'un après l'autre, vous aurez toujours deux threads utilisant cette entité.

Ainsi, si vous souhaitez verrouiller un objet de base de données donné afin qu'aucun autre thread ne puisse le modifier, vous devez utiliser des verrous de base de données.

Le synchronizedmot-clé ne fonctionne que dans une seule JVM. Si vous disposez de plusieurs nœuds Web, cela n'empêchera pas l'accès multi-thread sur plusieurs JVM.

Ce que vous devez faire est d'utiliser LockModeType.PESSIMISTIC_READouLockModeType.PESSIMISTIC_WRITE lors de l'application des modifications à la base de données, comme ceci:

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

Alors, voici ce que j'ai fait:

  • J'ai créé une nouvelle EntityTransactionet commencé une nouvelle transaction de base de données
  • J'ai chargé l' Postentité tout en maintenant un verrou sur l'enregistrement de base de données associé
  • J'ai changé d' Postentité et commis la transaction
  • Dans le cas d'un Exceptionrejet, j'ai annulé la transaction

Pour plus de détails sur les transactions ACID et de base de données, consultez également cet article .

Vlad Mihalcea
la source