JavaRush /Java-Blog /Random-DE /Level 26. Antworten auf Interviewfragen zum Levelthema. T...
zor07
Level 31
Санкт-Петербург

Level 26. Antworten auf Interviewfragen zum Levelthema. Teil 2. Fragen 6-9, 11-12

Veröffentlicht in der Gruppe Random-DE
Level 26. Antworten auf Interviewfragen zum Levelthema.  Teil 2. Fragen 6–9, 11–12 – 1

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 zusammengefasst java.util.concurrent. Sie können schematisch nach Funktionalität wie folgt unterteilt werden: Level 26. Antworten auf Interviewfragen zum Levelthema.  Teil 2. Fragen 6-9, 11-12 - 2Gleichzeitige Sammlungen – eine Reihe von Sammlungen, die in einer Multithread-Umgebung effizienter arbeiten als standardmäßige universelle Sammlungen aus java.utildem Paket. Anstelle eines einfachen Wrappers Collections.synchronizedListmit 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 synchronizedBasismechanismen dar . Atomics – Klassen mit Unterstützung für atomare Operationen an Grundelementen und Referenzen. Quelle:waitnotifynotifyAll

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 und Hashtable-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. synhronizedHashMapConcurrentModificationExceptionintsynchronizedset/getvolatileAtomic*synchronized/volatile

8. Wie funktioniert die ConcurrentHashMap-Klasse?

Zum Zeitpunkt seiner Einführung ConcurrentHashMapbenö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
Die wichtigsten Umsetzungsideen ConcurrentHashMapsind wie folgt:
  1. Kartenelemente

    Im Gegensatz zu Elementen werden in HashMapals deklariert . Dies ist ein wichtiges Feature, auch aufgrund von Änderungen im JMM .EntryConcurrentHashMapvolatile

    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];
        }
    }
  2. Hash-Funktion

    ConcurrentHashMapAußerdem kommt eine verbesserte Hashing-Funktion zum Einsatz.

    Ich möchte Sie daran erinnern, wie es in HashMapJDK 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.

  3. 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.

  4. 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.

  5. Gesamt

    Die wichtigsten Vorteile und Implementierungsmerkmale ConcurrentHashMap:

    • hashmapDie 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, valueder als deklariert istvolatile
    Quelle: Funktionsweise von ConcurrentHashMap

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 verpackt java.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 wird
  • boolean 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 Methode lock()wartet sie nicht darauf, eine Sperre zu erhalten, wenn keine verfügbar ist
  • void unlock():Entfernt die Sperre
  • Condition newCondition():gibt das Objekt zurück Condition, das der aktuellen Sperre zugeordnet ist
Die Organisation des Sperrens im allgemeinen Fall ist recht einfach: Um die Sperre zu erhalten, wird die Methode aufgerufen lock(), und nach Abschluss der Arbeit mit gemeinsam genutzten Ressourcen wird die Methode aufgerufen unlock(), die die Sperre aufhebt. Mit dem Objekt Conditionkönnen Sie die Blockierung verwalten. Um mit Sperren zu arbeiten, wird in der Regel eine Klasse ReentrantLockaus 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 Resourcemit 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 Runnableund 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-finallyum 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 implementiert synchronized. Wenn wir einen synchronisierten Block schreiben, ersetzt ihn der Java-Compiler durch drei Codeteile:
  1. Am Anfang des Blocks synchronizedwird Code hinzugefügt, der den Mutex als beschäftigt markiert.
  2. Am Ende des Blocks synchronizedwird ein Code hinzugefügt, der den Mutex als frei markiert.
  3. Vor dem Block synchronizedwird Code hinzugefügt, der prüft, ob der Mutex beschäftigt ist. Anschließend muss der Thread warten, bis er freigegeben wird.
Teil 1
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION