Heim  >  Artikel  >  Java  >  Interviewfragen zu Java-Multithreading und Parallelität (1 bis 3 Fragen, mit Antworten)

Interviewfragen zu Java-Multithreading und Parallelität (1 bis 3 Fragen, mit Antworten)

(*-*)浩
(*-*)浩Original
2019-11-23 14:56:592683Durchsuche

Interviewfragen zu Java-Multithreading und Parallelität (1 bis 3 Fragen, mit Antworten)

1. DelayQueue verzögert unbegrenzte Blockierungswarteschlange

Wenn wir über die Verwendung und das Prinzip von DelayQueue sprechen, stellen wir zunächst DelayQueue vor. DelayQueue Ist eine unbegrenzte Blockierungswarteschlange Blockierungswarteschlange, aus der Elemente erst extrahiert werden können, wenn die Verzögerung abgelaufen ist. Der Kopf der Warteschlange ist das Delayed-Element, das nach Ablauf der Verzögerung am längsten gespeichert wird. (Empfohlene Studie: Java-Interview-Fragen)

DelayQueue-Blockierungswarteschlangen werden auch häufig in unserer Systementwicklung verwendet, zum Beispiel: Das Design des Cache-Systems, die Objekte im Cache, überschreiten die Leerlaufzeit muss aus dem Cache verschoben werden; das Aufgabenplanungssystem kann die Ausführungszeit der Aufgabe genau erfassen. Möglicherweise müssen wir viele zeitkritische Daten über Threads verarbeiten.

Wenn wir normale Threads verwenden, müssen wir alle Objekte einzeln durchlaufen, um festzustellen, ob die Daten abgelaufen sind. Erstens ist die Ausführungseffizienz nicht zu hoch Dies hat auch großen Einfluss auf die Genauigkeit der Daten. Eine Aufgabe, die um 12:00 Uhr ausgeführt werden muss, wird möglicherweise erst um 12:01 Uhr ausgeführt, was für Systeme mit hohen Datenanforderungen größere Nachteile mit sich bringt. Daraus können wir DelayQueue verwenden.

Im Folgenden finden Sie eine Einführung in DelayQueue und ein Beispiel. Und stellen Sie eine Implementierung der Delayed-Schnittstelle und Beispielcode bereit. DelayQueue ist eine BlockingQueue, deren spezialisierter Parameter Delayed ist.

(Studenten, die BlockingQueue nicht kennen, lernen bitte zuerst BlockingQueue kennen und lesen dann diesen Artikel) Der Vergleichsbenchmark ist der Verzögerungszeitwert der Implementierungsklasse der verzögerten Schnittstelle sollte ein fester Wert (endgültig) sein. DelayQueue wird intern mit PriorityQueue implementiert.

DelayQueue=BlockingQueue+PriorityQueue+Delayed

Die Schlüsselelemente von DelayQueue sind BlockingQueue, PriorityQueue und Delayed. Man kann sagen, dass DelayQueue eine BlockingQueue ist, die mithilfe einer Prioritätswarteschlange (PriorityQueue) implementiert wird. Der Vergleichsbasiswert der Prioritätswarteschlange ist die Zeit.

Ihre grundlegenden Definitionen lauten wie folgt

public interface Comparable<T> {
    public int compareTo(T o);
}
public interface Delayed extends Comparable<Delayed> {
    long getDelay(TimeUnit unit);
}
public class DelayQueue<E extends Delayed> implements BlockingQueue<E> {
    private final PriorityQueue<E> q = new PriorityQueue<E>();
}

Die interne Implementierung von DelayQueue verwendet eine Prioritätswarteschlange. Wenn die Angebotsmethode von DelayQueue aufgerufen wird, wird das Delayed-Objekt zur Prioritätswarteschlange q hinzugefügt. Wie folgt:

public boolean offer(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        E first = q.peek();
        q.offer(e);
        if (first == null || e.compareTo(first) < 0)
            available.signalAll();
        return true;
    } finally {
        lock.unlock();
    }
}

Die Take-Methode von DelayQueue entfernt den ersten der Prioritätswarteschlange q (Peek). Wenn der Verzögerungsschwellenwert nicht erreicht wird, wird eine Warteverarbeitung durchgeführt. Wie folgt:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (; ; ) {
            E first = q.peek();
            if (first == null) {
                available.await();
            } else {
                long delay = first.getDelay(TimeUnit.NANOSECONDS);
                if (delay > 0) {
                    long tl = available.awaitNanos(delay);
                } else {
                    E x = q.poll();
                    assert x != null;
                    if (q.size() != 0)
                        available.signalAll(); //wake up other takers return x;
                }
            }
        }
    } finally {
        lock.unlock();
    }
}

DelayQueue-Instanzanwendung

Ps: Um ein Aufrufverhalten zu haben, müssen die in DelayDeque gespeicherten Elemente die Delayed-Schnittstelle erben. Die Delayed-Schnittstelle macht das Objekt zu einem verzögerten Objekt, wodurch das in der DelayQueue-Klasse gespeicherte Objekt ein Aktivierungsdatum erhält. Diese Schnittstelle erzwingt die folgenden zwei Methoden.

Im Folgenden wird Verzögerung verwendet, um einen Cache zu implementieren. Es umfasst insgesamt drei Klassen: Pair, DelayItem, Cache

Pair-Klasse:

public class Pair<K, V> {
    public K first;
    public V second;
    public Pair() {
    }
    public Pair(K first, V second) {
        this.first = first;
        this.second = second;
    }
}

Das Folgende ist die Implementierung der Delay-Schnittstelle :

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class DelayItem<T> implements Delayed {
    /**
     * Base of nanosecond timings, to avoid wrapping
     */
    private static final long NANO_ORIGIN = System.nanoTime();
    /**
     * Returns nanosecond time offset by origin
     */
    final static long now() {
        return System.nanoTime() - NANO_ORIGIN;
    }
    /**
     * Sequence number to break scheduling ties, and in turn to guarantee FIFO order among tied
     * entries.
     */
    private static final AtomicLong sequencer = new AtomicLong(0);
    /**
     * Sequence number to break ties FIFO
     */
    private final long sequenceNumber;
    /**
     * The time the task is enabled to execute in nanoTime units
     */
    private final long time;
    private final T item;
    public DelayItem(T submit, long timeout) {
        this.time = now() + timeout;
        this.item = submit;
        this.sequenceNumber = sequencer.getAndIncrement();
    }
    public T getItem() {
        return this.item;
    }
    public long getDelay(TimeUnit unit) {
        long d = unit.convert(time - now(), TimeUnit.NANOSECONDS); return d;
    }
    public int compareTo(Delayed other) {
        if (other == this) // compare zero ONLY if same object return 0;
            if (other instanceof DelayItem) {
                DelayItem x = (DelayItem) other;
                long diff = time - x.time;
                if (diff < 0) return -1;
                else if (diff > 0) return 1;
                else if (sequenceNumber < x.sequenceNumber) return -1;
                else
                    return 1;
            }
        long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS));
        return (d == 0) ?0 :((d < 0) ?-1 :1);
    }
}

Das Folgende ist die Implementierung von Cache, einschließlich Put- und Get-Methoden

import javafx.util.Pair;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Cache<K, V> {
    private static final Logger LOG = Logger.getLogger(Cache.class.getName());
    private ConcurrentMap<K, V> cacheObjMap = new ConcurrentHashMap<K, V>();
    private DelayQueue<DelayItem<Pair<K, V>>> q = new DelayQueue<DelayItem<Pair<K, V>>>();
    private Thread daemonThread;
    public Cache() {
        Runnable daemonTask = new Runnable() {
            public void run() {
                daemonCheck();
            }
        };
        daemonThread = new Thread(daemonTask);
        daemonThread.setDaemon(true);
        daemonThread.setName("Cache Daemon");
        daemonThread.start();
    }
    private void daemonCheck() {
        if (LOG.isLoggable(Level.INFO)) LOG.info("cache service started.");
        for (; ; ) {
            try {
                DelayItem<Pair<K, V>> delayItem = q.take();
                if (delayItem != null) {
                    // 超时对象处理
                    Pair<K, V> pair = delayItem.getItem();
                    cacheObjMap.remove(pair.first, pair.second); // compare and remove
                }
            } catch (InterruptedException e) {
                if (LOG.isLoggable(Level.SEVERE)) LOG.log(Level.SEVERE, e.getMessage(), e);
                break;
            }
        }
        if (LOG.isLoggable(Level.INFO)) LOG.info("cache service stopped.");
    }
    // 添加缓存对象
    public void put(K key, V value, long time, TimeUnit unit) {
        V oldValue = cacheObjMap.put(key, value);
        if (oldValue != null) q.remove(key);
        long nanoTime = TimeUnit.NANOSECONDS.convert(time, unit);
        q.put(new DelayItem<Pair<K, V>>(new Pair<K, V>(key, value), nanoTime));
    }
    public V get(K key) {
        return cacheObjMap.get(key);
    }
}

Testen Sie die Hauptmethode:

// 测试入口函数
public static void main(String[] args) throws Exception {
    Cache<Integer, String> cache = new Cache<Integer, String>();
    cache.put(1, "aaaa", 3, TimeUnit.SECONDS);
    Thread.sleep(1000 * 2);
    {
        String str = cache.get(1);
        System.out.println(str);
    }
    Thread.sleep(1000 * 2);
    {
        String str = cache.get(1);
        System.out.println(str);
    }
}

Das Ausgabeergebnis ist:

aaaa
null

Wir sehen das obige Ergebnis. Wenn die Verzögerungszeit überschritten wird, gehen die Daten im Cache automatisch verloren und das Ergebnis wird gelöscht null sein.

2. Gleichzeitige (Sammel-)Warteschlange – nicht blockierende Warteschlange

Nicht blockierende Warteschlange

Zuerst müssen wir Verstehen Sie es einfach Als Nächstes erfahren Sie, was eine nicht blockierende Warteschlange ist:

Im Gegensatz zur blockierenden Warteschlange wird die Ausführung der nicht blockierenden Warteschlange nicht blockiert, unabhängig davon, ob es sich um das Entfernen von Verbrauchern oder den Beitritt von Produzenten handelt . Unter der Haube nutzen nicht blockierende Warteschlangen CAS (Compare and Swap), um eine nicht blockierende Thread-Ausführung zu erreichen.

Einfache Vorgänge für nicht blockierende Warteschlangen

Wie bei blockierenden Warteschlangen sind übliche Methoden in nicht blockierenden Warteschlangen das Entfernen und Einreihen in die Warteschlange.

offer(): Eine von der Warteschlangenschnittstelle geerbte Methode, die den Warteschlangeneintragsvorgang implementiert und die Ausführung des Threads nicht behindert, gibt sie true zurück;

poll(): Verschieben Sie den Kopfknotenzeiger, geben Sie das Kopfknotenelement zurück und entfernen Sie das Kopfknotenelement aus der Warteschlange. Geben Sie null zurück , geben Sie das Kopfknotenelement zurück. Wenn die Warteschlange leer ist, wird null zurückgegeben

3. Nicht blockierender Algorithmus

Zuerst müssen wir pessimistisches Sperren und optimistisches Sperren verstehen

Pessimistisches Sperren: Angenommen, die Parallelitätsumgebung ist pessimistisch, die Konsistenz wird zerstört, daher müssen Konflikte vollständig sein durch Exklusivsperren verboten. Es gibt eine klassische Metapher: „Wenn du die Tür nicht abschließt, bricht der Unruhestifter ein und macht Chaos.“ Also „man kann immer nur eine Person auf einmal öffnen und hereinlassen, damit man einen behalten kann.“ ein Auge auf ihn haben.

Optimistisches Sperren: Es wird davon ausgegangen, dass die Parallelitätsumgebung optimistisch ist, das heißt, obwohl es Parallelitätskonflikte gibt, können die Konflikte entdeckt werden und verursachen keinen Schaden Nachdem die Parallelitätskonflikte festgestellt wurden, erfolgt ein Neustart oder ein erneuter Versuch. Eine Analogie lautet: „Wenn Sie die Tür nicht abschließen, werden die Unruhestifter zwar einbrechen, aber sie werden wissen, ob sie Sie zerstören wollen.“ Daher können Sie einfach alle hereinlassen und warten, bis Sie herausfinden, dass sie es tun zerstören wollen. „Eine Entscheidung treffen“.

Es wird allgemein angenommen, dass die Leistung des optimistischen Sperrens höher ist als die des pessimistischen Sperrens, insbesondere in einigen komplexen Szenarien. Dies liegt hauptsächlich daran, dass pessimistische Sperren auch bestimmte Vorgänge schützen, die beim Sperren keinen Schaden anrichten. Während der Wettbewerb um optimistische Sperren nur bei kleinsten Parallelitätskonflikten auftritt, ist die Granularität der pessimistischen Sperren am geringsten “. Der Entwurf optimistischer Sperren ist jedoch häufig komplexer, sodass in komplexen Szenarien häufig pessimistische Sperren verwendet werden. Stellen Sie zunächst die Korrektheit sicher und verfolgen Sie dann gegebenenfalls die Leistung.

Die Implementierung des optimistischen Sperrens erfordert häufig Hardwareunterstützung. Die meisten Prozessoren implementieren eine CAS-Anweisung, um die Semantik von „Compare And Swap“ zu implementieren (Swap bedeutet hier „Swap in“, also festlegen). Bildung eines grundlegenden optimistischen Schlosses. CAS enthält 3 Operanden:

Der zu lesende und zu schreibende Speicherort V

Der zu vergleichende Wert A

Der neue zu schreibende Wert B

CAS aktualisiert den Wert von Position V genau dann atomar mit dem neuen Wert B, wenn der Wert von Position V gleich A ist; andernfalls wird keine Operation ausgeführt. Unabhängig davon, ob der Wert der Position V gleich A ist, wird der ursprüngliche Wert von V zurückgegeben. Eine interessante Tatsache ist, dass „die Verwendung von CAS zur Steuerung der Parallelität“ nicht gleichbedeutend mit der „Verwendung optimistischer Sperren“ ist. CAS ist lediglich ein Mittel, um sowohl optimistisches als auch pessimistisches Sperren zu erreichen. Optimismus und Pessimismus sind lediglich Strategien zur Parallelitätskontrolle.

Das obige ist der detaillierte Inhalt vonInterviewfragen zu Java-Multithreading und Parallelität (1 bis 3 Fragen, mit Antworten). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn