In der Multi-Threading-Stiftung mit hoher Parallelität Java II haben wir zunächst grundlegende Thread-Synchronisationsoperationen erwähnt. Was wir diesmal erwähnen möchten, ist das Synchronisierungssteuerungswerkzeug im gleichzeitigen Paket.
1. Verwendung verschiedener Synchronisationskontrollwerkzeuge
1.1 Reentrantlock
Reentrantlock fühlt sich wie eine verbesserte Version von Synchronized an. Das Synchronisierungsmerkmal ist, dass es einfach zu verwenden ist und alles für die Verarbeitung der JVM überlassen bleibt, aber seine Funktionen sind relativ schwach. Vor JDK1.5 war Reentrantlocks Leistung besser als synchronisiert. Aufgrund der Optimierung des JVM ist die Leistung der beiden in der aktuellen JDK -Version vergleichbar. Wenn es sich um eine einfache Implementierung handelt, verwenden Sie Reentrantlock nicht absichtlich.
Im Vergleich zu Synchronisierten ist Reentrantlock funktionell reicher und hat die Eigenschaften von Wiedereintritt, unterbrechbar, begrenzter Zeit und fairer Verriegelung.
Lassen Sie uns zunächst ein Beispiel verwenden, um die anfängliche Verwendung von Reentrantlock zu veranschaulichen:
Pakettest; import Java.util.Concurrent public static int i = 0; @Override public void run () {for (int j = 0; j <10000000; j ++) {lock.lock (); Versuchen Sie {i ++; } endlich {lock.unlock (); }}} public static void main (String [] args) löst unterbrochene Ausnahme {Test test = new Test (); Thread T1 = neuer Thread (Test); Thread T2 = neuer Thread (Test); t1.start (); t2.Start (); t1.join (); t2.join (); System.out.println (i); }}Es gibt zwei Threads, die ++ Operationen auf i durchführen. Um die Sicherheit der Gewinde zu gewährleisten, wird Reentrantlock verwendet. Aus der Verwendung können wir erkennen, dass Reentrantlock im Vergleich zu Synchronisierten etwas komplizierter ist. Da der Entsperrvorgang schließlich ausgeführt werden muss, ist es möglich, dass der Code eine Ausnahme hat und die Sperre nicht veröffentlicht wird und die Synchronisierung von der JVM veröffentlicht wird.
Was sind die hervorragenden Eigenschaften von Reentrantlock?
1.1.1 Wiedereintritt
Ein einzelner Thread kann wiederholt eingegeben werden, muss jedoch wiederholt beendet werden
lock.lock (); lock.lock (); try {i ++; } endlich {lock.unlock (); lock.unlock ();}Da Reentrantlock ein Wiedereintrittsschloss ist, können Sie das gleiche Schloss wiederholt erhalten, das einen Schloss im Zusammenhang mit dem Schloss aufweist. Wenn ein Faden, dem das Schloss besitzt, erneut das Schloss erhält, wird der Akquisitionschalter um 1 erhöht und das Schloss muss zweimal freigegeben werden, um die reale Release (Wiedereintrittsschloss) zu erhalten. Dies ahmt die Semantik der Synchronisierung nach; Wenn der Thread in einen durch den Monitor geschützten synchronisierten Block eingeht, den der Thread bereits hat, darf der Thread fortgesetzt werden. Wenn der Thread den zweiten (oder nachfolgenden) synchronisierten Block verlässt, wird das Schloss nicht freigegeben. Das Schloss wird nur freigegeben, wenn der Thread den ersten synchronisierten Block verlässt, der durch den von ihm eintretenden Monitor geschützt ist.
öffentliches Klasse Kind erweitert Vater implementiert Runnable {endgültiges statisches Kind = new Child (); //, um sicherzustellen, dass eindeutige öffentliche statische void main (String [] args) {für (int i = 0; i <50; i ++) {neuer Thread (Child) .Start (); }} public synchronisierte void dosomething () {System.out.println ("1Child.dosomething ()"); doanotherthing (); // andere synchronisierte Methoden in Ihrer eigenen Klasse nennen} private synchronisierte void doanotherthing () {Super.dosomething (); // Die synchronisierte Methode des übergeordneten Klasse system.out.println ("3Child.doanotherthing ()") aufrufen; } @Override public void run () {child.dosomething (); }} class father {public synchronisierte void dosomething () {System.out.println ("2Father.dosomething ()"); }}Wir können sehen, dass ein Thread eine andere synchronisierte Methode eingibt und die zuvor erhaltenen Sperren nicht freigibt. Die Ausgabe ist also immer noch nacheinander. Synchronisiert ist daher auch ein Wiedereintrittschloss
Ausgabe:
1Child.Dosomething ()
2father.dosomething ()
3Child.Doanotherthing ()
1Child.Dosomething ()
2father.dosomething ()
3Child.Doanotherthing ()
1Child.Dosomething ()
2father.dosomething ()
3Child.Doanotherthing ()
...
1.1.2. Unterbrechbar
Im Gegensatz zu synchronisiert reagiert Reentrantlock auf Interrupts. Interrupt verwandte Wissensansicht [hohe Parallelität Java 2] Multithreading -Grundlagen
Ordinary lock.lock () kann nicht auf Interrupts reagieren, lock.Lockinterruptisle () kann auf Interrupts reagieren.
Wir simulieren eine Deadlock -Szene und verwenden dann Interrupts, um mit dem Deadlock umzugehen
Pakettest; Import Java.lang.Management.ManagementFactory; Import Java.lang.Management.Threadinfo; Import Java.lang.Management.ThreadmxBean; öffentliches statisches Reentrantlock lock2 = neuer Reentrantlock (); int Lock; public test (int lock) {this.lock = lock; } @Override public void run () {try {if (lock == 1) {lock1.lockInterruptisle (); try {thread.sleep (500); } catch (Ausnahme e) {// toDo: Behandeln Sie Ausnahme} lock2.lockinterruptical (); } else {lock2.lockinterruptisle (); try {thread.sleep (500); } catch (Ausnahme e) {// toDo: Behandeln Sie die Ausnahme} lock1.Lockinterruptible (); }} catch (Exception e) {// Todo: Handle -Ausnahme} endlich {if (lock1.isheldByCurrentThread ()) {lock1.unlock (); } if (lock2.isheldByCurrentThread ()) {lock2.unlock (); } System.out.println (Thread.currentThread (). GetId () + ": Thread Exit"); }} public static void main (String [] args) löscht InterruptedException aus {Test t1 = neuer Test (1); Test T2 = neuer Test (2); Thread Thread1 = neuer Thread (T1); Thread 2 = neuer Thread (T2); Thread1.Start (); thread2.Start (); Thread.sleep (1000); //Deadlockchecker.check (); } statische Klasse Deadlockchecker {private endgültige statische ThreadMxBean Mbean = ManagementFactory .GethreadmxBean (); endgültiger statischer Runnable Deadlockchecker = new Runnable () {@Override public void run () {// Todo automatisch generierte Methode Stub während (true) {long [] Deadlocked theadids = mbean.finddeadlocked threads (); if (Deadlocked threadids! für (Thread t: thread.getAllStacktraces (). keyset ()) {für (int i = 0; i <threadInfos.length; i ++) {if (t.getId () == ThreadInfos [i] .Gethreadid ()) {t.interrupt (); }}}}} try {thread.sleep (5000); } catch (Ausnahme e) {// toDo: Handle -Ausnahme}}}}}; public static void check () {Thread t = neuer Thread (Deadlockchecker); T.Setdaemon (wahr); t.start (); }}}Der obige Code kann Deadlocks verursachen, Thread 1 erhält Lock1, Thread 2 erhält Lock2 und dann möchte einander die Schlösser des anderen bekommen.
Wir verwenden JStack, um die Situation nach dem Ausführen des obigen Codes anzuzeigen
Tatsächlich wurde ein Deadlock entdeckt.
Der Deadlockchecker.Check (); Die Methode wird verwendet, um Deadlocks zu erkennen und dann den Deadlock -Thread zu unterbrechen. Nach der Unterbrechung verläuft der Faden normal.
1.1.3. Zeitlich begrenzt
Wenn das Timeout das Schloss nicht erhalten kann, wird er falsch zurückgegeben und warten nicht dauerhaft, um eine tote Schloss zu bilden.
Verwenden Sie Lock.tryLock (langfristige Zeit, Zeiteinheit), um zeitlich limitierbare Schlösser zu implementieren, wobei Parameter Zeit und Einheiten sind.
Lassen Sie mich Ihnen ein Beispiel geben, um zu veranschaulichen, dass die Zeit begrenzt werden kann:
Pakettest; import Java.util.Concurrent.TimeUnit; Import Java.util.Concurrent @Override public void run () {try {if (lock.trylock (5, TimeUnit.Seconds)) {Thread.sleep (6000); } else {System.out.println ("Get Lock fehlgeschlagen"); }} catch (exception e) {} endlich {if (lock.isheldByCurrentThread ()) {lock.unlock (); }}} public static void main (String [] args) {test t = neuer Test (); Thread T1 = neuer Thread (t); Thread T2 = neuer Thread (t); t1.start (); t2.Start (); }}Verwenden Sie zwei Threads, um um ein Schloss zu konkurrieren. Wenn ein Faden das Schloss erwirbt, schlafen Sie 6 Sekunden lang und jeder Faden versucht nur 5 Sekunden lang das Schloss.
Es muss also einen Faden geben, der das Schloss nicht erhalten kann. Wenn Sie es nicht bekommen können, beenden Sie direkt.
Ausgabe:
Holen Sie sich Schloss fehlgeschlagen
1.1.4. Faires Schloss
Wie man verwendet:
öffentliche Wiedereintrittsschloss (Boolesche Fair)
öffentliches statisches Reentrantlock Fairlock = neuer Reentrantlock (true);
Schlösser im Allgemeinen sind unfair. Es ist nicht unbedingt möglich, dass der Thread, der zuerst kommt, zuerst das Schloss erhalten kann, aber der später kommt, wird das Schloss später erhalten. Unfaire Schlösser können Hunger verursachen.
Ein faires Schloss bedeutet, dass dieses Schloss sicherstellen kann, dass der Faden an erster Stelle steht und das Schloss zuerst erhält. Obwohl faire Schlösser keinen Hunger verursachen, wird die Leistung von fairen Schlössern viel schlechter sein als die von Nicht-Fair-Schlösser.
1.2 Zustand
Die Beziehung zwischen Zustand und Wiedereintritt ähnelt synchronisiert und Objekt.wait ()/signal ().
Durch die Awiit () -Methode wird der aktuelle Thread die aktuelle Sperre warten. Wenn das Signal () in anderen Threads oder bei der SignalAll () -Methode verwendet wird, wird der Thread wieder auf die Sperre zurückgeführt und werden weiter ausgeführt. Oder wenn der Faden unterbrochen wird, können Sie auch aus dem Warten herausspringen. Dies ist der Methode von Object.wait () sehr ähnlich.
Die AwaitUtUnterrupticle () -Methode entspricht im Grunde genommen mit der Await () -Methode, aber sie wartet nicht auf die Reaktion unterbrach während des Prozesses. Die Singal () -Methode wird verwendet, um einen Thread zu warten, der wartet. Die relative Singalall () -Methode wird alle Threads warten. Dies ist sehr ähnlich der Methode objct.notify ().
Ich werde es hier nicht ausführlich vorstellen. Lassen Sie mich Ihnen ein Beispiel geben, um zu veranschaulichen:
Pakettest; importieren java.util.concurrent.locks.condition; import java.util.concurrent öffentliche statische Bedingung Zustand = lock.newcondition (); @Override public void run () {try {lock.lock (); Condition.aait (); System.out.println ("Thread ist los"); } catch (Ausnahme e) {e.printstacktrace (); } endlich {lock.unlock (); }} public static void main (String [] args) löst InterruptedException aus {test t = new Test (); Thread = neuer Thread (t); Thread.Start (); Thread.Sleep (2000); lock.lock (); Condition.Signal (); lock.unlock (); }}Das obige Beispiel ist sehr einfach. Lassen Sie einen Faden warten und lassen Sie ihn den Hauptfaden aufwachen. Condition.aait ()/Signal kann erst nach Erhalt des Schlosses verwendet werden.
1.3.Semaphor
Für Schlösser ist es sich gegenseitig ausschließlich. Es bedeutet, dass niemand es wieder bekommen kann, solange ich das Schloss bekomme.
Für Semaphor können mehrere Threads gleichzeitig den kritischen Abschnitt eingeben. Es kann als gemeinsames Schloss betrachtet werden, aber die gemeinsame Grenze ist begrenzt. Nachdem die Grenze aufgebraucht ist, blockieren andere Threads, die die Grenze nicht erhalten haben, noch außerhalb des kritischen Bereichs. Wenn der Betrag 1 ist, ist es gleichwertig zu sperren
Hier ist ein Beispiel:
Pakettest; import Java.util.concurrent.executorService; Import java.util.concurrent.executors; Import Java.util.Concurrent @Override public void run () {try {semaphore.acquire (); Thread.Sleep (2000); System.out.println (Thread.currentThread (). GetId () + "done"); } catch (Ausnahme e) {e.printstacktrace (); } endlich {semaphore.release (); }} public static void main (String [] args) löst InterruptedException aus. endgültiger Test t = neuer Test (); für (int i = 0; i <20; i ++) {ExecutorService.Submit (t); }}}Es gibt einen Thread -Pool mit 20 Threads, und jeder Thread geht zur Lizenz von Semaphor. Es gibt nur 5 Lizenzen für Semaphor. Nach dem Laufen sehen Sie, dass 5 in Stapeln ausgeben, Stapel ausgegeben werden.
Natürlich kann ein Thread auch mehrere Lizenzen gleichzeitig beantragen
öffentliche Leere Erwerb (INT Genehmigungen) löst die InterruptedException aus
1.4 ReadWriteLock
ReadWriteLock ist ein Schloss, das Funktionen unterscheidet. Lesen und Schreiben sind zwei verschiedene Funktionen: Das Lesen des Lesens schlägt sich nicht gegenseitig aus, das Readschreiber schließt sich gegenseitig aus, und das Schreibschreiben schließt sich gegenseitig aus.
Dieses Design erhöht die Parallelität und gewährleistet die Datensicherheit.
Wie man verwendet:
private statische ReentranTreadWriteLock ReadWriteLock = New ReentranTreadWriteLock ();
private static Lock Readlock = ReadWriteLock.readlock ();
private static lock writeelock = readWriteLock.WriteLock ();
Ausführliche Beispiele können Sie die Java -Implementierung von Produzenten- und Verbraucherproblemen sowie Leser- und Schriftstellern anzeigen, und ich werde sie hier nicht erweitern.
1.5 Countdownlatch
Ein typisches Szenario für einen Countdown -Timer ist ein Raketenstart. Bevor die Rakete auf den Markt gebracht wird, werden häufig Inspektionen verschiedener Geräte und Instrumente durchgeführt, um sicherzustellen, dass alles narrensicher ist. Der Motor kann erst nach Abschluss aller Inspektionen zündeten. Dieses Szenario eignet sich sehr für Countdownlatch. Dadurch warten Sie, dass der Zündungs -Thread vor der Ausführung alle Überprüfthreads abgeschlossen ist
Wie man verwendet:
statische endgültige Countdownlatch -End = neuer Countdownlatch (10);
End.Countdown ();
end.aait ();
Schematisches Diagramm:
Ein einfaches Beispiel:
Pakettest; import Java.util.Concurrent.Countdownlatch; importieren java.util.concurrent statischer endgültiger Test t = neuer Test (); @Override public void run () {try {thread.sleep (2000); System.out.println ("komplett"); Countdownlatch.Countdown (); } catch (Ausnahme e) {e.printstacktrace (); }} public static void main (String [] args) löst InterruptedException aus {ExecutorService ExecutorService = Executors.NewFixedThreadpool (10); für (int i = 0; i <10; i ++) {ExecutorService.execute (t); } Countdownlatch.await (); System.out.println ("Ende"); ExecutorService.Shutdown (); }}Der Haupt -Thread muss warten, bis alle 10 Threads ausgeführt werden, bevor sie "Ende" ausgeben.
1.6 Cyclicbarrier
Ähnlich wie beim Countdownlatch wartet es auch darauf, dass einige Threads abgeschlossen werden, bevor sie ausgeführt werden. Der Unterschied zum Countdownlatch besteht darin, dass dieser Zähler wiederholt verwendet werden kann. Angenommen, wir setzen den Zähler auf 10. Nachdem wir dann die erste Stapel von 10 Threads gesammelt haben, kehrt der Zähler auf Null zurück und sammelt dann die nächste Stapel von 10 Threads
Wie man verwendet:
Public Cyclicbarrier (int Partys, Runnable Barrieraction)
Barrieraction ist die Aktion, die das System ausführt, wenn der Zähler einmal zählt.
erwarten()
Schematisches Diagramm:
Hier ist ein Beispiel:
Pakettest; import Java.util.Concurrent Privates Final Cyclicbarrier Cyclic; öffentlicher Test (String Soldier, Cyclicbarrier Cyclic) {this.Soldier = Soldat; this.cyclic = cyclic; } @Override public void run () {try {// Warte darauf, dass alle Soldaten cyclic.await () ankommen; Dowork (); // Warten Sie darauf, dass alle Soldaten ihre Arbeit zyklisch vervollständigen. } catch (Ausnahme e) {// Todo automatisch generierter Block E. printstacktrace (); }} private void dowork () {// Todo automatisch generierte Methode Stub try {thread.sleep (3000); } catch (Ausnahme e) {// toDo: Handle -Ausnahme} System.out.println (Soldat + ": Done"); } public static class Barrierrun implementiert Runnable {boolesche Flagge; int n; public barrierrun (boolesche Flagge, int n) {Super (); this.flag = Flag; this.n = n; } @Override public void run () {if (flag) {System.out.println (n + "Aufgabeabschluss"); } else {System.out.println (n + "Abschluss festlegen"); Flag = wahr; }}} public static void main (String [] args) {endgültig int n = 10; Thread [] threads = neuer Thread [n]; boolesche Flagge = Falsch; CyclicBarrier -Barriere = neuer CyclicBarrier (N, New Barrierrun (Flag, N)); System.out.println ("set"); für (int i = 0; i <n; i ++) {System.out.println (i+"report"); Threads [i] = neuer Thread (neuer Test ("Soldier" + i, Barriere); Themen [i] .Start (); }}}Druckergebnis:
versammeln
0 Berichte
1 Bericht
2 Berichte
3 Berichte
4 Berichte
5 Berichte
6 Berichte
7 Berichte
8 Berichte
9 Berichte
10 Sätze kompletter Soldat 5: Fertig
Soldat 7: fertig
Soldat 8: fertig
Soldat 3: fertig
Soldat 4: fertig
Soldat 1: fertig
Soldat 6: fertig
Soldat 2: fertig
Soldat 0: fertig
Soldat 9: fertig
10 Aufgaben erledigt
1.7 Locksupport
Faden blockieren primitiv
Ähnlich wie die Suspend
Locksupport.park ();
Locksupport.unpark (t1);
Im Vergleich zur Suspendierung ist es nicht einfach, das Einfrieren von Faden zu verursachen.
Die Idee von Locksupport ist Semaphor etwas ähnlich. Es hat eine interne Lizenz. Sie nimmt diese Lizenz weg, wenn sie geparkt ist, und beantragt diese Lizenz, wenn sie nicht entspricht. Wenn Unpark vor dem Park ist, tritt nicht ein Gefrierpunkt auf.
Der folgende Code ist der Suspend-Beispielcode in der Multi-Threading-Foundation [hohe Parallelität Java 2]. Bei der Verwendung von Suspend tritt ein Deadlock auf.
Pakettest; import java.util.concurrent.locks.locksupport; public class test {statisches Objekt u = new Object (); statische testsuspendthread t1 = neue testsuspendthread ("t1"); statische testsuspendthread t2 = neue testsuspendthread ("t2"); public static class testsuspendThread erweitert Thread {public testsuspendThread (String -Name) {setName (name); } @Override public void run () {synchronized (u) {System.out.println ("in" + getName ()); //Thread.CurrentThread (). Suspend (); Locksupport.park (); }}} public static void main (String [] args) löst unterbrochene Ausnahme {t1.start () aus; Thread.Sleep (100); t2.Start (); // t1.resume (); // t2.Resume (); Locksupport.unpark (t1); Locksupport.unpark (t2); t1.join (); t2.join (); }}Die Verwendung von Locksupport führt jedoch nicht zu Deadlocks.
Zusätzlich
Park () kann auf Interrupts reagieren, aber keine Ausnahmen auswirft. Das Ergebnis der Interrupt -Antwort ist, dass die Rückkehr der park () -Funktion das Interrupt -Flag von Thread erhalten kann. Intrupted ().
Es gibt viele Orte in JDK, an denen Park verwendet wird. Natürlich wird die Implementierung von Locksupport auch mit unsicherem.park () implementiert.
public static void Park () {
unsicher.Park (false, 0l);
}
1.8 Implementierung der Wiedereintrittsprotokolle
Lassen Sie uns die Implementierung von Reentrantlock einführen. Die Implementierung von Reentrantlock besteht hauptsächlich aus drei Teilen:
Die übergeordnete Klasse von Reentrantlock verfügt über eine staatliche Variable, die den synchronen Zustand darstellt.
/*** Der Synchronisationszustand. */ privates volatiles Int -Zustand;
Stellen Sie den Zustand fest, um das Schloss durch den CAS -Betrieb zu erwerben. Wenn der Sperrhalter auf 1 eingestellt ist, wird der aktuelle Thread übergeben
endgültige void lock () {if (vergleicheStSetState (0, 1)) setExclusiveOwnerThread (thread.currentThread ()); sonst erwerben (1); }Wenn das Schloss nicht erfolgreich ist, wird eine Bewerbung gestellt
public final void erwerben (int arg) {if (! tryacquire (arg) && accoderqueued (addwaiter (node.exclusive), arg)) selfterrupt (); }Versuchen Sie zunächst Tryacquire nach dem Auftragen, da ein anderer Thread möglicherweise das Schloss veröffentlicht hat.
Wenn Sie sich noch nicht für das Schloss beworben haben, fügen Sie den Kellner hinzu, was bedeutet, sich der wartenden Warteschlange hinzuzufügen
privater Knoten addwaiter (Knotenmodus) {Node node = new node (thread.currentThread (), modus); // Versuchen Sie den schnellen Pfad von ENQ; Backup auf volle ENQ auf Fehlerknoten Pred = Tail; if (pred! = null) {node.prev = pred; if (vergleicheStettail (Pred, Knoten)) {Pred.Next = Node; Return Node; }} enq (Knoten); Return Node; }Während dieser Zeit werden viele Versuche gegeben, ein Schloss zu beantragen, und wenn Sie sich immer noch nicht bewerben können, werden Sie aufgehängt.
private final boolean ParkandCheckinterrupt () {locksupport.park (this); return thread.interrupted (); }In ähnlicher Weise wird hier, wenn das Schloss veröffentlicht wird und dann Unpark hier nicht ausführlich besprochen wird.
2. gleichzeitiger Container und typische Quellcodeanalyse
2.1 Concurrenthashmap
Wir wissen, dass HashMap kein fadensicherer Behälter ist. Der einfachste Weg, Hashmap-Thread-Safe zu machen, ist die Verwendung
Sammlungen.SynchronizedMap, es ist eine Wrapper für Hashmap
öffentliche statische Karte m = sammeln.synchronisierteMap (neuer Hashmap ());
In ähnlicher Weise bietet SET für die Liste auch ähnliche Methoden.
Diese Methode ist jedoch nur für Fälle geeignet, in denen die Parallelitätsmenge relativ gering ist.
Schauen wir uns die Implementierung von synchronisiertenMap an
private endgültige Karte <k, v> m; // Backing Map Final Object mutex; // Objekt, an dem SynchronizedMap (MAP <K, v> m) {if (m == null) synchronisiert werden soll. this.m = m; mutex = this; } this.Mutex = mutex; } public int size() { synchronized (mutex) { return m.size();} } public boolean isEmpty() { synchronized (mutex) {return m.isEmpty();} } public boolean containsKey(Object key) { synchronized (mutex) {return m.containsKey(key);} } public boolean containsValue(Object value) { synchronisiert (mutex) {return m.containSValue (value);}} public v get (Objektschlüssel) {synchronisiert (mutex) {return m.get (key);}} public v put (k key, v value) {synchronized (mutex) {return m.put (taste, value); M.Remove (Schlüssel);}} public void putall (map <? Erweitert k,? Erweitert v> map) {synchronisiert (mutex) {m.putall (map);}} public void clear () {synchronized (mutex) {m.clear ();}}}}}}}}}}}}}}}}}}}}}}Es wickelt die Hashmap innen und synchronisierte dann jede Operation des Hashmap.
Da jede Methode das gleiche Schloss (mutex) erwerbt, bedeutet dies, dass Operationen wie Put und Entfernen sich gegenseitig ausschließen und die Menge an Parallelität erheblich verringern.
Lassen Sie uns sehen, wie die gleichzeitige Implementierung der Vereinbarung
public v put (k key, v value) {segment <k, v> s; if (value == null) werfen neue nullpointerexception (); int Hash = Hash (Schlüssel); int j = (Hash >>> SegmentShift) & Segmentmask; if ((s = (Segment <k, v>) unsicher.GetObject // nichtflüchtig; resecheck (Segmente, (j << sShift) + sbase) == null) // in seureSment s = seuresegment (j); Return s.put (Schlüssel, Hash, Wert, Falsch); }Es gibt ein Segmentsegment innerhalb der Concurrenthashmap, das die große Hashmap in mehrere Segmente (kleines Hashmap) unterteilt und dann die Daten zu jedem Segment hash. Auf diese Weise müssen die Hash-Operationen mehrerer Threads in verschiedenen Segmenten eine Gewindefache sein, sodass Sie nur die Threads im selben Segment synchronisieren müssen, wodurch die Trennung von Schlössern realisiert und die Zuschauer erheblich erhöht wird.
Es wird schwieriger sein, wenn sie Concurrenthashmap.Size verwenden, da es die Datensumme jedes Segments zählen muss. Zu diesem Zeitpunkt müssen Sie jedem Segment Sperren hinzufügen und dann Datenstatistiken durchführen. Dies ist ein kleiner Nachteil nach der Trennung des Schlosses, aber die Größenmethode sollte nicht bei hoher Frequenz aufgerufen werden.
In Bezug auf die Implementierung verwenden wir nicht synchronisierte und lock.lock, sondern trylock so viel wie möglich. Gleichzeitig haben wir auch einige Optimierungen bei der Implementierung von HashMap vorgenommen. Ich werde es hier nicht erwähnen.
2.2 Blockingqueue
Blockingqueue ist kein Hochleistungsbehälter. Aber es ist ein sehr guter Container zum Teilen von Daten. Es ist eine typische Implementierung von Produzenten und Verbrauchern.
Schematisches Diagramm:
Einzelheiten können Sie die Java -Implementierung von Produzenten- und Verbraucherproblemen sowie Leser- und Schriftstellernproblemen überprüfen.