Lorsque la bibliothèque de classes Java ne fournit pas d'outil de synchronisation approprié, vous devez créer un outil de synchronisation personnalisé.
Structure de blocage des opérations dépendant de l'état
Copiez le code comme suit :
acquérir le verrou sur l'état de l'objet ; // Demande d'acquisition du verrou
while(la précondition ne tient pas){//La précondition n'est pas remplie
libérer le verrou ;//libérer le verrou en premier
attendre que la condition préalable soit remplie ;//attendre que la condition préalable soit remplie
éventuellement échouer en cas d'interruption ou d'expiration du délai ; // L'exécution a échoué en raison d'une interruption ou d'un délai d'attente
réacquérir le verrou ;//Réessayer d'acquérir le verrou
}
effectuer une action//exécuter
déverrouiller le verrou;//libérer le verrou
Exemple de classe de base d'implémentation de cache limité
Copiez le code comme suit :
classe publique BaseBoundBuffer<V> {
V[] final privé buf ;
queue int privée ;
tête int privée ;
nombre d'ints privés ;
@SuppressWarnings("non coché")
public BaseBoundBuffer (int capacité) {
buf = (V[]) nouvel objet[capacité];
}
public synchronisé void doPut(V v) {
buf[queue] = v;
si (++tail == buf.length)
queue = 0 ;
compte++;
}
public synchronisé V doTake() {
V v = buf[tête];
si (++head == buf.length)
tête = 0 ;
compter--;
retourner v;
}
public final synchronisé booléen isFull() {
return count == buf.length;
}
public final synchronisé booléen isEmpty() {
nombre de retour == 0 ;
}
}
Méthode d'implémentation bloquante 1 : lancer une exception à l'appelant
Copiez le code comme suit :
public synchronisé void put1 (V v) lève une exception {
si (est Plein ())
throw new Exception("erreur complète");
doPut(v);
}
Analyse : les exceptions doivent être utilisées lorsque des exceptions se produisent. Il n'est pas approprié de lancer des exceptions ici ; l'appelant doit gérer la situation dans laquelle la précondition échoue, ce qui ne résout pas le problème fondamental.
Méthode de mise en œuvre du blocage 2 : via l'interrogation et la mise en veille
Copiez le code comme suit :
public void put2 (V v) lance InterruptedException {
while (true) {//Sondage
synchronisé (ce) {
si (!isFull()) {
doPut(v);
retour;
}
}
Thread.sleep(SLEEP_TIME);//Sleep
}
}
Analyse : Il est difficile de peser le paramètre SLEEP_TIME du temps de sommeil. Si le paramètre est trop petit, le processeur peut interroger plusieurs fois, consommant plus de ressources CPU ; si le paramètre est trop grand, la réactivité sera moindre.
Troisième méthode de mise en œuvre du blocage : file d'attente conditionnelle
Les éléments de la file d'attente des conditions sont des threads qui attendent les conditions associées une par une. Chaque objet Java peut être utilisé comme verrou, et chaque objet peut également être utilisé comme file d'attente de conditions, et les méthodes wait, notify et notifyAll dans Object constituent l'API de la file d'attente de conditions interne. Object.wait libérera automatiquement le verrou et demandera au système d'exploitation de suspendre le thread actuel afin que d'autres threads puissent obtenir le verrou et modifier l'état de l'objet. Object.notify et Object.notifyAll peuvent réveiller le thread en attente, sélectionner un thread dans la file d'attente des conditions pour le réveiller et essayer de réacquérir le verrou.
Copiez le code comme suit :
public synchronisé void put3 (V v) lance InterruptedException {
pendant que (est Plein ())
attendez();
doput(v);
notifierTout();
}
Analyse : obtenez une meilleure réponse, simple et facile à utiliser.
Utiliser la file d'attente conditionnelle
1. Prédicat conditionnel
1).Définition : Un prédicat conditionnel est une condition préalable pour qu'une opération devienne une opération dépendante de l'état. Un prédicat conditionnel est une expression composée de variables d'état individuelles dans la classe. Par exemple, le prédicat conditionnel de la méthode put est « le cache n'est pas vide ».
2).Relation : il existe une relation ternaire importante dans l'attente conditionnelle, comprenant le verrouillage, la méthode d'attente et un prédicat conditionnel. Plusieurs variables d'état sont incluses dans un prédicat conditionnel, et chaque variable d'état doit être protégée par un verrou, le verrou doit donc être maintenu avant de tester le prédicat conditionnel. L'objet verrou et l'objet file d'attente conditionnelle (et l'objet sur lequel les méthodes wait et notify sont appelées) doivent être le même objet.
3) Contraintes : Chaque appel à attendre sera implicitement associé à un prédicat de condition spécifique. Lorsqu'un prédicat de condition spécifique est appelé, l'appelant doit déjà détenir un verrou lié à la file d'attente des conditions. Ce verrou doit également protéger le prédicat de condition du composant. variables d'état
2. Règles d'utilisation de la file d'attente conditionnelle
1). Il existe généralement un prédicat conditionnel
2).Toujours tester les prédicats conditionnels avant d'appeler wait, et tester à nouveau après le retour de l'attente ;
3) .Toujours appeler wait dans la boucle ;
4) Assurez-vous que les variables d'état qui constituent le prédicat de condition sont protégées par un verrou, et ce verrou doit être associé à la file d'attente de conditions ;
5). Lors de l'appel de wait, notify et notifyAll, le verrou associé à la file d'attente des conditions doit être maintenu ;
6). Après avoir vérifié le prédicat conditionnel, ne relâchez pas le verrou avant de commencer à exécuter la logique protégée ;
3.Notifications
Essayez d'utiliser notifyAll au lieu de nofify, car nofify réveillera aléatoirement un thread de l'état dormant à l'état bloqué (l'état bloqué est un thread qui essaie toujours d'acquérir le verrou, c'est-à-dire qu'une fois qu'il constate que le verrou est disponible, il le fait. maintiendra le verrou immédiatement), tandis que notifyAll réveillera tous les threads de la file d'attente de conditions de l'état dormant à l'état bloqué. Considérez cette situation, si le thread A entre dans l'état dormant en raison du prédicat de condition Pa et le thread B. entre dans l’état dormant en raison du prédicat de condition Pb. est vrai, le thread C exécute une seule notification. Si la JVM sélectionne aléatoirement le thread A pour se réveiller, alors le thread A vérifie que le prédicat conditionnel Pa n'est pas vrai et entre ensuite en état de veille. À partir de ce moment-là, aucun autre thread ne peut être réveillé. , et le programme sera toujours en état de veille si vous l'utilisez. notifyAll est différent. La JVM réveillera tous les threads en attente dans la file d'attente de conditions de l'état de veille à l'état bloqué. Même si un thread est sélectionné au hasard et entre en état de veille parce que le prédicat de condition n'est pas vrai, d'autres threads seront en compétition pour le verrou et. continuez l'exécution. Descendez.
4. Le code de copie standard de la méthode de dépendance d'état est le suivant :
void stateDependentMethod throwsInterruptedException{
synchronisé(verrouillage){
while(!conditionPredicate))
lock.wait();
}
//faire quelque chose();
....
notifierTout();
}
Afficher l'objet Condition
L'objet Condition explicite est une alternative plus flexible et offre des fonctionnalités plus riches : plusieurs attentes peuvent exister sur chaque verrou, les attentes conditionnelles peuvent être interrompues ou ininterrompues, les attentes basées sur le temps et les opérations de file d'attente équitables ou injustes. Une condition peut être associée à un verrou, tout comme une file d'attente de conditions est associée à un verrou intégré. Pour créer une Condition, appelez la méthode Lock.newCondition sur le Lock associé. Le code suivant est utilisé pour réimplémenter le cache limité à l'aide de variables de condition d'affichage. Le code est le suivant :
classe publique ConditionBoundedBuffer<V> {
V[] final privé buf ;
queue int privée ;
tête int privée ;
nombre d'ints privés ;
verrou privé = new ReentrantLock();
condition privée notFullCondition = lock.newCondition();
condition privée notEmptyCondition = lock.newCondition();
@SuppressWarnings("non coché")
public ConditionBoundedBuffer (int capacité) {
buf = (V[]) nouvel objet[capacité];
}
public void doPut (V v) lance InterruptedException {
essayer {
lock.lock();
while (compte == longueur buf)
notFullCondition.attendre();
buf[queue] = v;
si (++tail == buf.length)
queue = 0 ;
compte++;
notEmptyCondition.signal();
} enfin {
lock.unlock();
}
}
public V doTake() lance InterruptedException {
essayer {
lock.lock();
tandis que (compte == 0)
notEmptyCondition.await();
V v = buf[tête];
buf[tête] = nul;
si (++head == buf.length)
tête = 0 ;
compter--;
notFullCondition.signal();
retourner v;
} enfin {
lock.unlock();
}
}
}