1 Qu'est-ce qu'un problème de concurrence.
Plusieurs processus ou threads accédant simultanément à la même ressource (ou dans la même période) causeront des problèmes de concurrence.
Un exemple typique est que deux opérateurs bancaires exploitent le même compte en même temps. Par exemple, les opérateurs A et B lisent un compte avec un solde de 1 000 yuans en même temps, l'opérateur A ajoute 100 yuans au compte, l'opérateur B réduit également 50 yuans pour le compte, A se soumet d'abord, et B se soumet plus tard. Le solde du compte réel final est de 1000-50 = 950 yuan, mais il aurait dû être de 1000 + 100-50 = 1050. Il s'agit d'un problème de concurrence typique. Comment le résoudre? Peut utiliser des verrous.
2Usage de synchronisé en java
Utilisation 1
Test de classe publique {public synchronisé void print () {….;}}Si un thread exécute la méthode print (), l'objet sera verrouillé. D'autres threads ne pourront pas exécuter tous les blocs synchronisés de l'objet.
Utilisation 2
Test de classe publique {public void print () {synchronisé (this) {// verrouiller cet objet…;}}} Même utilisation 1, mais il peut mieux refléter l'essence de l'utilisation synchronisée.
Utilisation 3
Test de classe publique {String privé a = "test"; public void print () {synchronisé (a) {// verrouiller un objet ...;}} public synchronisé void t () {…; // Ce bloc de code synchronisé ne sera pas verrouillé à cause de print ().}}L'exécution de print () verrouillera l'objet a. Notez qu'il ne verrouille pas l'objet du test, ce qui signifie que d'autres méthodes synchronisées de l'objet de test ne seront pas verrouillées en raison de l'impression (). Une fois le bloc de code de synchronisation exécuté, le verrouillage vers A est libéré.
Afin de verrouiller le bloc de code d'un objet sans affecter l'écriture haute performance d'autres blocs synchronisés de l'objet:
Test de classe publique {octet privé [] lock = new octet [0]; public void print () {synchronisé (lock) {…;}} public synchronisé void t () {…;}}Verrouillage de la méthode statique
Test de classe publique {public synchronisé statique void execute () {…;}}Le même effet
Test de classe publique {public static void execute () {synchronisé (testThread.class) {…;}}}3 serrures à Java et faire la queue pour aller aux toilettes.
Le verrouillage est un moyen d'empêcher d'autres processus ou threads d'accéder aux ressources, c'est-à-dire que la ressource verrouillée ne peut pas être accessible par d'autres demandes. En Java, le mot-clé sychronisé est utilisé pour verrouiller un objet. Par exemple:
classe publique MyStack {int idx = 0; char [] data = new char [6]; public synchronisé void push (char c) {data [idx] = c; idx ++;} public synchronisé char pop () {idx -; return data [idx];} public static void main (string args []) {mystack m = new mystack (); À strictement parler, tous les blocs d'objet synchronisés sont verrouillés. S'il y a un autre thread T essayant d'accéder à M, alors t ne peut pas exécuter les méthodes push et pop de l'objet m. * / m.pop (); // objet m est verrouillé. }}Le déverrouillage de verrouillage de Java est exactement le même que plusieurs personnes qui font la queue pour des toilettes publiques. La première personne a verrouillé la porte de l'intérieur après être entrée, donc les autres ont dû faire la queue. La porte ne s'ouvrira (déverrouiller) que lorsque la première personne sortira après la fin. C'était le tour de la deuxième personne d'entrer, et il verrouillerait à nouveau la porte de l'intérieur, et les autres continueraient à faire la queue.
Il est facile de comprendre en utilisant la théorie des toilettes: lorsqu'une personne entre dans les toilettes, les toilettes seront verrouillées, mais cela ne fera pas verrouiller les autres toilettes, car une personne ne peut pas s'accroupir dans deux toilettes en même temps. Pour Java, cela signifie: les serrures de Java sont ciblées dans le même objet, pas en classe. Voir l'exemple suivant:
MyStatCkm1 = newMyStack (); myStatCkm2 = newMyStatck (); m1.pop (); m2.pop ();
Les serrures des objets M1 n'affecteront pas les serrures de M2 car ce ne sont pas la même position de toilette. C'est-à-dire que s'il y a 3 threads T1, T2 et T3 opérant M1, ces 3 threads ne peuvent que faire la queue et attendre sur M1. En supposant que les 2 autres threads T8, T9 fonctionnant M2, puis T8, T9 n'attendra que sur M2. T2 et T8 n'ont rien à voir avec cela. Même si la serrure sur M2 est libérée, T1, T2 et T3 devront peut-être encore faire la queue sur M1. Il n'y a aucune raison, ce n'est pas le même siège de toilette.
Java ne peut pas ajouter deux verrous à un bloc de code en même temps. Ceci est différent du mécanisme de verrouillage de la base de données. La base de données peut ajouter plusieurs verrous différents à un enregistrement en même temps.
4 Quand dois-je libérer la serrure?
Généralement, le verrouillage est libéré après l'exécution du bloc de code de synchronisation (bloc de code verrouillé), ou le verrou peut être libéré à mi-chemin via la méthode Wait (). La méthode d'attente () est comme s'accroupir dans les toilettes à mi-chemin et soudain j'ai trouvé que l'égout était bloqué. Je devais sortir et me retirer pour que le réparateur d'égouts (un fil qui est prêt à exécuter les avis) puisse entrer et nettoyer les toilettes. Après le déblocage, le maître a crié: "Il a été réparé." Le camarade qui est sorti à nouveau s'est aligné après l'avoir entendu. Faites attention, vous devez attendre que le maître sorte. Si le maître ne sort pas, personne ne peut entrer. C'est-à-dire, après avoir avisé, d'autres threads peuvent entrer dans la zone bloquée et se déplacer immédiatement, mais d'autres threads peuvent entrer après la zone bloquée où le code de notification est exécuté et libéré pour libérer le verrou.
Voici l'exemple d'attente et de notification du code:
public synchronisé char pop () {char c; while (tamper.size () == 0) {try {this.wait (); // hors des toilettes} catch (interruptedException e) {// ignorez-le…}} c = ((caractères) buffer.remove (buffer.size () - 1)). charvalue (); return c;} public synchronisé void push (char c) {this.notify (); // informer ces threads wait () pour faire la queue à nouveau. Remarque: il suffit de les informer à la requeule. Caractère charobj = nouveau caractère (c); buffer.addelement (charobj);} // exécuter et libérer le verrou. Ces threads en file d'attente peuvent entrer.Allez plus loin.
En raison de l'opération d'attente (), les camarades qui sont sortis à mi-chemin ne feraient pas la queue jusqu'à ce qu'ils aient reçu le signal d'avis. Il regardait les gens faire la queue à côté de lui (le maître de réparation des tuyaux d'eau est également parmi eux). Notez que le maître de réparation d'eau ne peut pas aller sur la ligne, et il doit faire la queue comme ceux qui vont aux toilettes. Ce n'est pas qu'après qu'une personne s'accroupie à mi-chemin, le maître de réparation des tuyaux d'eau peut soudainement apparaître et entrer pour le réparer immédiatement. Il veut rivaliser équitablement avec les personnes qui faisaient la queue à l'origine, car il est également un fil ordinaire. Si le maître de réparation des tuyaux d'eau est bordé, la personne devant constatera qu'elle est bloquée et attendez, puis sortez et se tiendra à l'écart, attendez, sortez, ne vous retirez et n'allez que chez le maître pour entrer et exécuter les avis. De cette façon, après un certain temps, il y aura un tas de personnes debout à côté de la file d'attente, en attendant la notification.
Enfin, le maître est entré et a informé. Quelle est la prochaine étape?
1. Une personne attendue (fil) est informée.
2. Pourquoi est-ce qu'il est informé au lieu d'une autre personne d'attente? Dépend du JVM. Nous ne pouvons pas préemptionner
Déterminer lequel sera informé. En d'autres termes, ceux qui ont une grande priorité peuvent ne pas être éveillés en premier, en attendant
Chaque fois n'est pas nécessairement éveillé en premier, tout est imprévisible! (Bien sûr, si vous connaissez le JVM
S'il est implémenté, vous pouvez le prédire).
3. Il (le fil notifié) doit à nouveau faire la queue.
4. Sera-t-il en premier lieu de la ligne? La réponse est: je ne sais pas. Sera-t-il le dernier? Pas nécessairement.
Mais si la priorité du thread est réglée pour être relativement élevée, la probabilité de son rang d'abord est relativement élevée.
5. Quand il est à son tour de rentrer dans le siège des toilettes, il continuera à exécuter à partir du dernier endroit d'attente () et ne réexécutera pas.
Pour le dire de manière dégoûtante, il continuera à divaguer, pas pour se promener à nouveau.
6. Si le maître notifyall (), alors toutes les personnes qui ont abandonné à mi-chemin s'aligneront à nouveau. L'ordre est inconnu.
Javadoc dit que TheWakenedThreadswillNotBeBETOPROCEDETUNTHETHETHECURRENTSTRADRELINSHTHTHELOCKONSORSHISOBJECT (le thread éveillé ne peut pas être exécuté avant que le thread actuel ne libère le verrou).
Ceci est évident pour expliquer l'utilisation de la théorie des sièges de toilette.
Utilisation de 5 blocs
Utilisez le mot-clé synchronisé pour verrouiller les ressources. Le mot clé de verrouillage est également OK. Il est nouveau dans JDK1.5. L'utilisation est la suivante:
Class BoundedBuffer {Final Lock Lock = new RentrantLock (); Final Condition Notull = Lock.NewCondition (); Final Condition NotEmpty = Lock.NewCondition (); Final Object [] items = New Object [100]; int putPtr, TakePtr, Count; Public Void put (Object X) lance InterruptedException {Lock.Lock notull.await (); items [putptr] = x; if (++ putptr == items.length) putptr = 0; ++ count; notEmpty.signal ();} enfin {lock.unlock ();}} public objet prenant () lance InterruptedException {Lock.Lock items [TakePtr]; if (++ TakePtr == items.Length) TakePtr = 0; - count; notull.signal (); return x;} enfin {lock.unlock ();}}}(Remarque: Ceci est un exemple de Javadoc, un exemple d'implémentation d'une file d'attente de blocage. La file d'attente de blocage dite de blocage signifie que si une file d'attente est pleine ou vide, elle provoquera le blocage et l'attente du fil. Le ArrayBlockingQueue en Java offre une file d'attente de blocage prêt à l'emploi, et vous n'avez pas besoin d'écrire l'un d'eux spécifiquement.)
Le code entre lock.lock () et lock.unlock () d'un objet sera verrouillé. Quelle est la meilleure chose de cette méthode par rapport à la synchronisation? En bref, il classe les fils d'attente. Pour le décrire en utilisant la théorie du siège des toilettes, ceux qui s'accroupissent à mi-chemin et sortent du siège des toilettes et attendent peut avoir des raisons différentes. Certains le sont parce que les toilettes sont bloquées, et certaines sont dues aux toilettes qui sont hors de l'eau. Lors de l'informez, vous pouvez crier: si vous attendez que les toilettes soient bloquées, vous serez en ligne (par exemple, le problème des toilettes bloqué a été résolu), ou crier, si vous attendez que les toilettes soient bloquées, vous serez en ligne (par exemple, le problème des toilettes à bloquer a été résolu). Cela permet un contrôle plus détaillé. Contrairement à l'attente et à la notification en synchronisation, que les toilettes soient bloquées ou que les toilettes ne soient pas aqueuses, vous ne pouvez que crier: j'ai juste attendu ici pour faire la queue! Si les gens en ligne entrent et regardent, ils constatent que le problème des toilettes est simplement résolu, et le problème qu'ils sont impatients de résoudre (les toilettes n'ont pas d'eau) n'a pas encore été résolue, donc ils doivent revenir en arrière (attendre), et entrer dans une marche en vain, perdre du temps et des ressources.
La relation correspondante entre la méthode de verrouillage et synchronisé:
Lockawaitsignalsignalall
synchroniséwaitnotifynotifyall
Remarque: N'appelez pas d'attente, notifiez, notifyall dans les blocs verrouillés par verrouillage
6. Utilisez des pipelines pour communiquer entre les fils
Le principe est simple. Deux threads, l'un exploite PipedInputStream et l'autre fonctionne PipedOutputStream. Les données écrites par PipedOutputStream sont d'abord mise en cache dans le tampon. Si le tampon est plein, ce fil attend. PipedInputStream lit les données du tampon. Si le tampon n'a pas de données, ce thread attend.
La même fonction peut être obtenue en bloquant les files d'attente dans JDK1.5.
Package io; Importer java.io. *; public class PipedStreamTest {public static void main (String [] args) {PipedOutStream ops = new PipedOutStream (); pipedInputStream pis = new PipedInputStream (); try {ops.connect (pis); // implément pipeline connection newt produner (ops) .run (); Consumer (PIS) .Run ();} catch (exception e) {e.printStackTrace ();}}} // producteur Producer Producer implémente Runnable {private PipedOutputStream Ops; public producteur (PipedOutStstream ops) {this.ops = ops;} public Void run () {try {ops.write ("hell, spel" .getBytes ()); ops.close ();} catch (exception e) {e.printStackTrace ();}}} // Consumer Class Consumer implémente le pis) {this.PisStream Pis;} public void PiceStream Pis) {this.pis = pis;} run () {try {byte [] bu = new byte [100]; int len = pis.read (bu); System.out.println (new String (bu, 0, len)); pis.close ();} catch (exception e) {e.printStackTrace ();}}}}Exemple 2: Un petit changement dans le programme ci-dessus devient deux threads.
Package io; Importer java.io. *; public class PipedStreamTest {public static void main (String [] args) {pipedoutputStream ops = new PipedOutputStream (); pipedInputStream Pis = new PipedInputStream (); try {ops.connect (pis); // implémentation de connexion pipeline productrice p = new producteur (ops); new Thread (p) .start (); consommateur c = nouveau consommateur (pis); nouveau thread (c) .start ();} catch (exception e) {e.printStackTrace ();}}}}} // producteur de producteur implémente les ops ultimes) {this. run () {try {for (;;) {Ops.Write ("Hell, Spell" .getBytes ()); Ops.Close ();}}} // Consumer Class Consumer implémente Runnable {Private PipedInputStream pis;} public Void (PipedInputStream Pis) {pour; bu = new Byte [100]; int len = Pis.read (bu); System.out.println (new String (bu, 0, len));} Pis.close ();} catch (exception e) {e.printStackTrace ();}}}Exemple 3. Cet exemple est plus approprié à l'application
Importer java.io. *; classe publique Pipedio {// Une fois le programme en cours d'exécution, copiez le contenu du fichier SendFile dans le fichier de récepteur public static void Main (String args []) {try {// Construire l'objet de flux de lecture et d'écriture pipedinputStream Pis = new PipedInputStream (); PipedoutStream pos = new PipEdOutStStream (); pos.connect (pis); // construire deux threads et démarrer. Nouvel expéditeur (pos, "c: /text2.txt"). start (); nouveau récepteur (pis, "c: /text3.txt"). start ();} catch (ioexception e) {System.out.println ("Erreur de pipe" + e);}}}} // thread SendSed Sender Sender {PipedOutputStream POS; File File File; pos, string filename Name) {this.pos = pos; file = new File (filename);} // thread run méthode public void run () {try {// read Fichier Content FileInputStream fs = new FileInputStream (file); int data; while ((data = fs.read ())! = - 1) {// Écrivez le pipe start start pos.write (data);} pos.close ();} catch (ioException e) {System.out.println ("Erreur de l'expéditeur" + e);}}}} // récepteur de classe de thread étend le thread {PipedInputStream Pis; File (fichier nom);} // thread exécute public void run () {try {// write file stream object fileoutputStream fs = new fileoutputStream (file); int data; // lire while ((data = pis.read ())! = - 1) {// écrire dans le fichier local fs.write (data);} Pis.close ();} catch (ioException e) {System.out.println ("Erreur du récepteur" + e);}}}7 Fitre de blocage
La file d'attente de blocage peut remplacer la méthode d'écoulement du pipeline pour implémenter le mode de tuyau d'entrée / drainage (producteur / consommateur). JDK1.5 offre plusieurs files d'attente de blocage prêtes à l'emploi. Regardons maintenant le code de ArrayBlockingQueue comme suit:
Voici une file d'attente de blocage
BlockingQueue BlockingQ = Nouveau ArrayBlockingQueue 10;
Un fil tire de la file d'attente
pour (;;) {objet o = blockingq.take (); // La file d'attente est vide, puis attendez (blocage)}Un autre fil est stocké dans la file d'attente
pour (;;) {BlockingQ.put (nouvel objet ()); // Si la file d'attente est pleine, attendez (Blocking)}On peut voir que les files d'attente de blocage sont plus simples à utiliser que les pipelines.
8User les exécuteurs, l'exécuteur, l'exécutor-service, ThreadPoolExecutor
Vous pouvez utiliser des tâches de gestion de threads. Vous pouvez également utiliser un ensemble de classes fournies par JDK1.5 pour gérer plus facilement les tâches. À partir de ces catégories, nous pouvons vivre une façon de penser axée sur les tâches. Ces cours sont:
Interface de l'exécuteur. Comment utiliser:
Executor Executor = anExecutor; // Générez une instance exécutrice. Executor.Execute (new RunNableTask1 ());
Intention: les utilisateurs se concentrent uniquement sur l'exécution des tâches et n'ont pas à se soucier des détails de la création et de l'exécution des tâches, et d'autres problèmes qui concernent les implémenteurs tiers. C'est-à-dire, découpler l'exécution de l'appel de tâche et l'implémentation de la tâche.
En fait, il existe déjà d'excellentes implémentations de cette interface dans JDK1.5. Assez.
Les exécuteurs sont une classe d'usine ou une classe d'outils comme des collections, utilisées pour générer des instances de diverses interfaces.
L'interface ExecutorService est héritée de Executor.execUtor jette simplement la tâche dans Executor () pour l'exécution et ignore le reste. ExecutorService est différent, il fera plus de travail de contrôle. Par exemple:
class NetworksService {private final Serversocket Serversocket; Private Final ExecutorService Pool; public NetworksService (int port, int poolSize) lance ioException {serversocket = new serversocket (port); pool = exécutor.NewFixEdThreadpool (poolSize);} public Void Serve () {try {for (;;) {pool.execute (new Handler (serversocket.accept ()));}} catch (ioException ex) {pool.shutdown (); // aucune nouvelle tâche n'est exécutée}}} Handler de classe implémente Runnable {private final socket socket;Une fois ExecutorService (c'est-à-dire l'objet de pool dans le code) exécutant l'arrêt, il ne peut plus exécuter de nouvelles tâches, mais les anciennes tâches continueront d'être exécutées, et ceux qui attendent l'exécution n'attendront plus.
Soumancheur des tâches et communication de l'interprète
public static void main (String args []) lance l'exception {EMECTROGRORICE EMECTOROR = EMICAGETOR System.out.println (résultat); EMEMPOROR.SHUTDOWN ();}L'instance exécutrice obtenue par les exécuteurs.
Exécution de la tâche séquentiellement. Par exemple:
Executor.Submit (Task1); Executor.Submit (Task2);
Vous devez attendre que Task1 soit exécuté avant l'exécution de la tâche2.
Task1 et Task2 seront placés dans une file d'attente et traités par un fil de travail. Autrement dit: il y a 2 threads au total (thread principal, fil de travail qui traite les tâches).
Pour d'autres cours, veuillez vous référer à Javadoc
9 Contrôle des processus simultanés
Les exemples de cette section proviennent du tutoriel de concurrence Java de Wen Shao et peuvent être modifiés. Saluer à M. Wen.
Compteur de broches de porte à compte à rebours
Démarrez le fil et attendez que le fil se termine. Autrement dit, le thread principal commun et les autres threads enfants sont exécutés après la fin du thread principal.
public static void main (String [] args) lève une exception {// TODO Méthode générée automatique Stub final int count = 10; final Countdownlatch e complételatch = new CountdownLatch (count); // définir le nombre de verrous de porte est 10 pour (int i = 0; i <count; i ++) {Thread Thread = New Thread ("Worker Thread" + i) {public Void Run () {// Do xxxxxx "+ i) {public Void Run () { CompeteLatch.CountDown (); // Réduire un verrou de porte}}; Thread.Start ();} Completelatch.Await (); // Si le verrou de porte n'a pas encore été réduit, attendez. }Dans JDK1.4, la méthode commune est de définir l'état du thread de l'enfant et de la détection de boucle du thread principal. La facilité d'utilisation et l'efficacité ne sont pas bonnes.
Démarrez de nombreux fils et attendez que les notifications commencent
public static void main (String [] args) lève une exception {// TODO Méthode générée automatiquement Stume final CountdownLatch startlatch = new CountdownLatch (1); // définir un verrouillage de porte pour (int i = 0; i <10; i ++) {thread thread = new Thread ("Worker Thread" + i) {public Void run () {pas {startLatch.Await.Await (); Réduit encore, attendez} catch (InterruptedException e) {} // do xxxx}}; thread.start ();} startlatch.countDown (); // Réduire un verrou de porte}Cyclibarrier. Ce n'est qu'après que tous les threads atteignent une ligne de départ qu'ils peuvent continuer à fonctionner.
Classe publique Cyclibarriestrest implémente Runnable {Private Cyclibarrier Barrier; public Cyclibarriestrest (Cyclibarrier Barrier) {this.barrier = barrière;} public void run () {// Le thread vérifiera si tous les autres threads sont arrivés. Si vous n'arrivez pas, continuez à attendre. Lorsque tout est atteint, exécutez le contenu du corps de fonction d'exécution de la barrière} catch (exception e) {}} / ** * @param args * / public static void main (String [] args) {// paramètre 2 signifie que les deux threads ont atteint la ligne de départ avant de commencer à exécuter ensemble CyclicBarrier Barrier = New CyclicArrier (2, nouveau xxxx;}}); thread t1 = nouveau thread (new CyclibarriestTest (barrière)); thread t2 = nouveau thread (new Cyclibarriestrest (barrière)); t1.start (); t2.start ();}}Cela simplifie la façon traditionnelle d'implémenter cette fonction avec Counter + Wait / Notifyall.
10 Loi de la concurrence 3
La loi d'Amdahl. Compte tenu de l'échelle du problème, la partie de parallélisation représente 12%. Ensuite, même si le parallélisme est appliqué à l'extrême, les performances du système ne peuvent être améliorées que de 1 / (1-0.12) = 1,136 fois au maximum. Autrement dit: le parallélisme a une limite supérieure à l'amélioration des performances du système.
La loi de Gustafson. La loi de Gustafson affirme que la loi d'Amdahl ne considère pas que plus de puissance de calcul peut être utilisée à mesure que le nombre de processeurs augmente. L'essence consiste à modifier l'ampleur du problème afin que les 88% restants du traitement en série dans la loi d'Amdahl puissent être parallélisés, franchissant ainsi le seuil de performance. Essentiellement, c'est une sorte de temps d'échange d'espace.
Sun-Ni Law. Il s'agit d'une autre promotion des deux premières lois. L'idée principale est que la vitesse de l'informatique est limitée par la vitesse du stockage plutôt que par le processeur. Par conséquent, nous devons utiliser pleinement les ressources informatiques telles que l'espace de stockage et essayer d'augmenter l'échelle du problème pour produire des solutions meilleures / plus précises.
11 de simultanément au parallèle
Les ordinateurs doivent calculer rapidement lors de l'identification d'objets, afin que les puces deviennent chaudes et chaudes. Lorsque les gens reconnaissent les objets, ils sont clairs en un coup d'œil, mais ils ne font pas brûler une certaine cellule cérébrale (exagérée) et se sentent mal à l'aise. En effet, le cerveau est un système de fonctionnement parallèle distribué. Tout comme Google peut utiliser des serveurs Linux bon marché pour effectuer des calculs énormes et complexes, d'innombrables neurones dans le cerveau se calculent indépendamment, partageant les résultats les uns avec les autres et complétent instantanément l'effet qui nécessite des milliards de fonctions pour un CPU unique. Imaginez, s'il est créé dans le domaine du traitement parallèle, il aura un impact incommensurable sur le développement et l'avenir des ordinateurs. Bien sûr, les défis peuvent également être imaginés: de nombreux problèmes ne sont pas facilement "divisés".
Résumer
Ce qui précède est l'intégralité du contenu de cet article sur le problème de concurrence Java, et j'espère que cela sera utile à tout le monde. Les amis intéressés peuvent continuer à se référer à d'autres sujets connexes sur ce site. S'il y a des lacunes, veuillez laisser un message pour le signaler. Merci vos amis pour votre soutien pour ce site!