6. Was ist Cancarenzi?
Concurrency ist eine Klassenbibliothek in Java, die spezielle Klassen enthält, die für die Arbeit über mehrere Threads hinweg optimiert sind. Diese Klassen werden in einem Paket zusammengefasstjava.util.concurrent
. Sie können schematisch nach Funktionalität wie folgt unterteilt werden: java.util
dem Paket. Anstelle eines einfachen Wrappers Collections.synchronizedList
mit blockierendem Zugriff auf die gesamte Sammlung werden Sperren für Datensegmente verwendet oder die Arbeit wird mithilfe von wartefreien Algorithmen für das parallele Lesen von Daten optimiert. Warteschlangen – nicht blockierende und blockierende Warteschlangen mit Multithreading-Unterstützung. Nicht blockierende Warteschlangen sind auf Geschwindigkeit und Betrieb ausgelegt, ohne Threads zu blockieren. Blockierende Warteschlangen werden verwendet, wenn Sie die „Producer“- oder „Consumer“-Threads „verlangsamen“ müssen, wenn bestimmte Bedingungen nicht erfüllt sind, z. B. wenn die Warteschlange leer oder überfüllt ist oder kein freier „Consumer“ vorhanden ist. Synchronizer sind Hilfsprogramme zum Synchronisieren von Threads. Sie sind eine mächtige Waffe im „Parallel“-Computing. Executors – enthält hervorragende Frameworks zum Erstellen von Thread-Pools, zum Planen asynchroner Aufgaben und zum Erhalten von Ergebnissen. Sperren – stellen alternative und flexiblere Thread - Synchronisationsmechanismen im Vergleich zu den synchronized
Basismechanismen dar . Atomics – Klassen mit Unterstützung für atomare Operationen an Grundelementen und Referenzen. Quelle:wait
notify
notifyAll
7. Welche Klassen von Kankarensi kennen Sie?
Die Antwort auf diese Frage wird in diesem Artikel perfekt dargelegt . Ich sehe keinen Sinn darin, alles hier noch einmal abzudrucken, deshalb werde ich nur die Klassen beschreiben, mit denen ich die Ehre hatte, mich kurz vertraut zu machen. ConcurrentHashMap<K, V> – Im Gegensatz zu undHashtable
-Blöcken werden die Daten in Form von Segmenten dargestellt, unterteilt in Schlüssel-Hashes. Dadurch erfolgt der Datenzugriff segmentweise und nicht einzelner Objekte. Darüber hinaus stellen Iteratoren Daten für einen bestimmten Zeitraum dar und werfen keine . AtomicBoolean, AtomicInteger, AtomicLong, AtomicIntegerArray, AtomicLongArray – Was ist, wenn Sie in einer Klasse den Zugriff auf eine einfache Variable vom Typ synchronisieren müssen ? Sie können Konstrukte mit , und bei der Verwendung atomarer Operationen , verwenden . Aber Sie können es noch besser machen, indem Sie neue Klassen verwenden . Aufgrund der Verwendung von CAS sind Operationen mit diesen Klassen schneller als bei einer Synchronisierung über . Darüber hinaus gibt es Methoden zur atomaren Addition um einen bestimmten Betrag sowie zur Inkrementierung/Dekrementierung. synhronized
HashMap
ConcurrentModificationException
int
synchronized
set/get
volatile
Atomic*
synchronized/volatile
8. Wie funktioniert die ConcurrentHashMap-Klasse?
Zum Zeitpunkt seiner EinführungConcurrentHashMap
benötigten Java-Entwickler die folgende Hash-Map-Implementierung:
- Thread-Sicherheit
- Keine Sperren für die gesamte Tabelle beim Zugriff
- Es ist wünschenswert, dass beim Durchführen eines Lesevorgangs keine Tabellensperren vorhanden sind
ConcurrentHashMap
sind wie folgt:
-
Kartenelemente
Im Gegensatz zu Elementen werden in
HashMap
als deklariert . Dies ist ein wichtiges Feature, auch aufgrund von Änderungen im JMM .Entry
ConcurrentHashMap
volatile
static final class HashEntry<K, V> { final K key; final int hash; volatile V value; final HashEntry<K, V> next; HashEntry(K key, int hash, HashEntry<K, V> next, V value) { this .key = key; this .hash = hash; this .next = next; this .value = value; } @SuppressWarnings("unchecked") static final <K, V> HashEntry<K, V>[] newArray(int i) { return new HashEntry[i]; } }
-
Hash-Funktion
ConcurrentHashMap
Außerdem kommt eine verbesserte Hashing-Funktion zum Einsatz.Ich möchte Sie daran erinnern, wie es in
HashMap
JDK 1.2 war:static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
Version von ConcurrentHashMap JDK 1.5:
private static int hash(int h) { h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16); }
Warum muss die Hash-Funktion komplexer werden? Die Länge der Tabellen in einer Hash-Map wird durch eine Zweierpotenz bestimmt. Bei Hash-Codes, deren binäre Darstellungen sich nicht in der niedrigen und hohen Position unterscheiden, kommt es zu Kollisionen. Durch Erhöhen der Komplexität der Hash-Funktion wird dieses Problem lediglich gelöst und die Wahrscheinlichkeit von Kollisionen in der Karte verringert.
-
Segmente
Die Karte ist in N verschiedene Segmente unterteilt (standardmäßig 16, der Maximalwert kann 16 Bit betragen und ist eine Zweierpotenz). Jedes Segment ist eine threadsichere Tabelle mit Kartenelementen. Durch die Erhöhung der Anzahl der Segmente werden Änderungsvorgänge gefördert, die sich über mehrere Segmente erstrecken, wodurch die Wahrscheinlichkeit einer Blockierung zur Laufzeit verringert wird.
-
ConcurrencyLevel
Dieser Parameter beeinflusst die Speicherkartennutzung und die Anzahl der Segmente auf der Karte.
Die Anzahl der Segmente wird als nächste Zweierpotenz größer als concurrencyLevel gewählt. Durch die Senkung des concurrencyLevel steigt die Wahrscheinlichkeit, dass Threads beim Schreiben Kartensegmente blockieren. Eine Überschätzung des Indikators führt zu einer ineffizienten Speichernutzung. Wenn nur ein Thread die Karte ändert und der Rest liest, wird empfohlen, den Wert 1 zu verwenden.
-
Gesamt
Die wichtigsten Vorteile und Implementierungsmerkmale
ConcurrentHashMap
:hashmap
Die Karte verfügt über eine ähnliche Interaktionsschnittstelle wie- Lesevorgänge erfordern keine Sperren und werden parallel ausgeführt
- Schreibvorgänge können oft auch parallel ohne Blockierung durchgeführt werden
- Beim Erstellen wird der erforderliche Wert angegeben
concurrencyLevel
, der durch Lesen und Schreiben von Statistiken ermittelt wird - Kartenelemente haben einen Wert,
value
der als deklariert istvolatile
9. Was ist die Lock-Klasse?
Um den Zugriff auf eine gemeinsam genutzte Ressource zu steuern, können wir als Alternative zum synchronisierten Operator Sperren verwenden. Die Sperrfunktion ist in verpacktjava.util.concurrent.locks
. Zunächst versucht der Thread, auf die gemeinsam genutzte Ressource zuzugreifen. Wenn es frei ist, wird der Thread gesperrt. Sobald die Arbeit abgeschlossen ist, wird die Sperre für die gemeinsam genutzte Ressource aufgehoben. Wenn die Ressource nicht frei ist und bereits eine Sperre darauf gesetzt ist, wartet der Thread, bis diese Sperre aufgehoben wird. Sperrklassen implementieren eine Schnittstelle Lock
, die die folgenden Methoden definiert:
void lock():
wartet, bis die Sperre erworben wirdboolean tryLock():
versucht, eine Sperre zu erhalten; wenn die Sperre erhalten wird, wird true zurückgegeben . Wenn die Sperre nicht erworben wird, wird false zurückgegeben . Im Gegensatz zur Methodelock()
wartet sie nicht darauf, eine Sperre zu erhalten, wenn keine verfügbar istvoid unlock():
Entfernt die SperreCondition newCondition():
gibt das Objekt zurückCondition
, das der aktuellen Sperre zugeordnet ist
lock()
, und nach Abschluss der Arbeit mit gemeinsam genutzten Ressourcen wird die Methode aufgerufen unlock()
, die die Sperre aufhebt. Mit dem Objekt Condition
können Sie die Blockierung verwalten. Um mit Sperren zu arbeiten, wird in der Regel eine Klasse ReentrantLock
aus dem Paket verwendet. java.util.concurrent.locks.
Diese Klasse implementiert die Schnittstelle Lock
. Schauen wir uns die Verwendung der Java Lock API am Beispiel eines kleinen Programms an: Nehmen wir also an, wir haben eine Klasse Resource
mit einigen Thread-sicheren Methoden und Methoden, bei denen Thread-Sicherheit nicht erforderlich ist.
public class Resource {
public void doSomething(){
// пусть здесь происходит работа с базой данных
}
public void doLogging(){
// потокобезопасность для логгирования нам не требуется
}
}
Nehmen wir nun eine Klasse, die die Schnittstelle implementiert Runnable
und Klassenmethoden verwendet Resource
.
public class SynchronizedLockExample implements Runnable{
// экземпляр класса Resource для работы с методами
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
Schreiben wir nun das obige Programm mit der Lock-API anstelle der synchronized
.
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// класс для работы с Lock API. Переписан с приведенной выше программы,
// но уже без использования ключевого слова synchronized
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
// лочим на 10 секунд
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
//убираем лок
lock.unlock();
}
// Для логгирования не требуется потокобезопасность
resource.doLogging();
}
}
Wie Sie dem Programm entnehmen können, verwenden wir die Methode, tryLock()
um sicherzustellen, dass der Thread nur eine bestimmte Zeit wartet. Wenn es keine Sperre für das Objekt erhält, wird es einfach protokolliert und beendet. Ein weiterer wichtiger Punkt. Sie müssen einen Block verwenden, try-finally
um sicherzustellen, dass die Sperre auch dann aufgehoben wird, wenn die Methode doSomething()
eine Ausnahme auslöst. Quellen:
11. Was ist ein Mutex?
Ein Mutex ist ein spezielles Objekt zum Synchronisieren von Threads/Prozessen. Es kann zwei Zustände annehmen – beschäftigt und frei. Vereinfacht gesagt ist ein Mutex eine boolesche Variable, die zwei Werte annimmt: beschäftigt (wahr) und frei (falsch). Wenn ein Thread den exklusiven Besitz eines Objekts anstrebt, markiert er seinen Mutex als beschäftigt, und wenn er die Arbeit damit beendet hat, markiert er seinen Mutex als frei. An jedes Objekt in Java ist ein Mutex angehängt. Nur die Java-Maschine hat direkten Zugriff auf den Mutex. Es bleibt dem Programmierer verborgen.12. Was ist ein Monitor?
Ein Monitor ist ein spezieller Mechanismus (ein Stück Code) – ein Add-on über dem Mutex, der den korrekten Betrieb mit ihm gewährleistet. Schließlich reicht es nicht aus, zu markieren, dass das Objekt beschäftigt ist; wir müssen auch sicherstellen, dass andere Threads nicht versuchen, das beschäftigte Objekt zu verwenden. In Java wird der Monitor mit dem Schlüsselwort implementiertsynchronized
. Wenn wir einen synchronisierten Block schreiben, ersetzt ihn der Java-Compiler durch drei Codeteile:
- Am Anfang des Blocks
synchronized
wird Code hinzugefügt, der den Mutex als beschäftigt markiert. - Am Ende des Blocks
synchronized
wird ein Code hinzugefügt, der den Mutex als frei markiert. - Vor dem Block
synchronized
wird Code hinzugefügt, der prüft, ob der Mutex beschäftigt ist. Anschließend muss der Thread warten, bis er freigegeben wird.
GO TO FULL VERSION