texte
Lors de la programmation dans un environnement simultané, un mécanisme de verrouillage est nécessaire pour synchroniser les opérations entre plusieurs threads pour assurer un accès mutuellement exclusif aux ressources partagées. Le verrouillage peut causer des dommages aux performances, ce qui semble bien connu. Cependant, le verrouillage lui-même n'apporte pas beaucoup de consommation de performances, et les performances sont principalement le processus d'acquisition de serrures dans les threads. S'il n'y a qu'un seul fil en concurrence pour les verrous et qu'il n'y a pas de concurrence multi-thread à l'heure actuelle, le JVM optimisera, et la consommation de performances causée par le verrouillage peut être ignorée. Par conséquent, la normalisation du fonctionnement du verrouillage, l'optimisation de la méthode d'utilisation du verrouillage et l'évitement de la concurrence des fils inutiles, peuvent non seulement améliorer les performances du programme, mais également éviter la possibilité de délais de fil provoqués par le verrouillage irrégulier et améliorer la robustesse du programme. Ce qui suit explique plusieurs idées d'optimisation de verrouillage.
1. Essayez de ne pas verrouiller la méthode
Lorsqu'un verrou est ajouté à une fonction membre normale, le thread obtient le verrouillage de l'objet de l'objet où se trouve la méthode. À l'heure actuelle, l'objet entier sera verrouillé. Cela signifie également que si les multiples méthodes de synchronisation fournies par cet objet sont destinées à différents services, puisque l'objet entier est verrouillé, lorsqu'une entreprise est traitée, d'autres threads commerciaux non liés doivent également attendre. L'exemple suivant montre ceci:
La classe LockMethod contient deux méthodes de synchronisation, qui sont appelées dans deux processus métier:
classe publique LockMethod {public synchronisé void busia () {for (int i = 0; i <10000; i ++) {System.out.println (thread.currentThread (). getName () + "traiter avec les affaires A:" + i); }} public synchronisé void busIB () {for (int i = 0; i <10000; i ++) {System.out.println (thread.currentThread (). getName () + "traiter avec l'entreprise b:" + i); }}}BUSSA est une classe de threads utilisée pour gérer une entreprise et appelle la méthode Busia () de LockMethod:
La classe publique Bussb étend Thread {LockMethod LockMethod; void Deal (LockMethod LockMethod) {this.lockMethod = LockMethod; } @Override public void run () {super.run (); LockMethod.Busib (); }}La classe TestLockMethod utilise Thread Bussa et BussB pour le traitement des affaires:
classe publique TestLockMethod étend Thread {public static void main (String [] args) {LockMethod LockMethod = new LockMethod (); Bussa bussa = new bussa (); Bussb bussb = new bussb (); bussa.deal (LockMethod); bussb.deal (LockMethod); bussa.start (); bussb.start (); }}Lors de l'exécution du programme, vous pouvez voir que lors de l'exécution de Thread BUSSA, BUSSB ne peut pas entrer dans la fonction BUSSIB (), car le verrouillage d'objet LockMethod est obtenu par Thread Bussa.
2. Réduire le bloc de code synchrone et verrouiller uniquement les données
Parfois, pour la commodité en programmation, certaines personnes synchronisaient un grand morceau de code. Si certaines opérations dans ce bloc de code ne sont pas liées aux ressources partagées, elles doivent être placées à l'extérieur du bloc synchrone pour éviter de tenir les serrures pendant longtemps, ce qui fait rester d'autres threads dans un état d'attente. Surtout certaines opérations cyclables et les opérations d'E / S synchrones. Non seulement cela signifie réduire le bloc de synchronisation dans la plage de ligne du code, mais également dans la logique d'exécution, le bloc de synchronisation doit être réduit. Par exemple, ajoutez plus de jugements conditionnels et synchronisez s'ils remplissent les conditions, plutôt que de mener des jugements conditionnels après la synchronisation, afin de minimiser la logique inutile entrant dans le bloc de synchronisation.
3. Essayez de ne pas inclure les serrures dans la serrure
Cette situation se produit souvent. Une fois que le thread a obtenu le verrou A, il appelle la méthode de synchronisation d'un autre objet dans le bloc de méthode de synchronisation et obtient le deuxième verrou. Cela peut entraîner plusieurs demandes de verrouillage dans une pile d'appels. Dans le cas du multi-threading, il peut provoquer des exceptions très complexes et difficiles, entraînant des impasses. Le code suivant montre ceci:
synchronisé (a) {synchronisé (b) {}}Ou la méthode de synchronisation est appelée dans le bloc de synchronisation:
synchronisé (a) {b b = objArrayList.get (0); B.Method (); // c'est une méthode de synchronisation}La solution consiste à sauter et à ajouter des serrures, et n'incluez pas les verrous:
{B b = null; synchronisé (a) {b = objArrayList.get (0); } b.Method ();} 4. Privatiser le verrou et gérer le verrou en interne
Il est plus sûr d'utiliser la serrure comme objet privé, et il ne peut pas être obtenu de l'extérieur. Un objet peut être verrouillé directement par d'autres threads, et le thread maintient le verrouillage de l'objet de l'objet, par exemple:
classe A {public void method1 () {}} classe b {public void method1 () {a a = new a (); synchronisé (a) {// verrouillage directement a.Method1 (); }}}De cette manière d'utiliser, le verrouillage de l'objet de l'objet A est maintenu par l'extérieur, il est donc plus dangereux de laisser la serrure être utilisée à plusieurs endroits à l'extérieur, et cela entraîne également des problèmes à lire le flux logique du code. Une meilleure façon consiste à gérer les verrous eux-mêmes à l'intérieur de la classe et à fournir des opérations de synchronisation via des interfaces lorsque le schéma synchrone externe est nécessaire:
classe A {private object lock = new Object (); public void method1 () {synchronisé (lock) {}}} classe b {public void method1 () {a a = new a (); a.Method1 (); }} 5. Effectuer une décomposition de verrouillage appropriée
Considérez la procédure suivante:
public class Gameserver {public map <string, list <player>> tables = new HashMap <String, list <playing>> (); public void join (lecteur joueur, table table) {if (player.getAccountBalance ()> table.getLimit ()) {synchronisé (tables) {list <player> tablePlayers = tables.get (table.getId ()); if (tablePlayers.size () <9) {tablePlayers.add (lecteur); }}}}} public void Leave (joueur joueur, table table) {/ * omit * /} public void createTable () {/ * omit * /} public void destroyTable (table table) {/ * omit * /}}Dans cet exemple, la méthode de jointure n'utilise qu'un seul verrou de synchronisation pour obtenir l'objet List <player> dans les tableaux, puis déterminer si le nombre de joueurs est inférieur à 9. Si c'est le cas, ajoutez un joueur. Lorsqu'il y a des milliers de listes <ve joueur> dans les tables, la compétition pour les serrures de tables sera très féroce. Ici, nous pouvons envisager de décomposer le verrou: après avoir rapidement récupéré les données, verrouillez l'objet List <playing>, afin que d'autres threads puissent rapidement rivaliser pour obtenir le verrouillage de l'objet des tables:
classe publique Gameserver {
Carte publique <String,
List <player>> tables = new hashmap <string,
List <player>> ();
public void join (joueur joueur, table de table) {
if (player.getAccountBalance ()> table.getLimit ()) {
List <player> tablePlayers = null;
synchronisé (tableaux) {
TablePlayers = Tables.get (Table.GetId ());
}
synchronisé (table-jeu) {
if (tablePlayers.size () <9) {
Tableplayers.add (joueur);
}
}
}
}
Public vide congé (joueur joueur, table de table) {
/ * Omis * /
}
public void créetable () {
/ * Omis * /
}
public void destroyTable (table de table) {
/ * Omis * /
}
}