Die Klassenbibliothek selbst enthält viele Klassen mit Zustandsabhängigkeiten. Wie FutureTask, BlockingQueue usw. Einige Operationen in diesen Klassen basieren auf Zustandsvorbedingungen. Sie können beispielsweise keine Elemente aus einer leeren Warteschlange löschen oder das Berechnungsergebnis einer nicht abgeschlossenen Aufgabe abrufen. Bevor Sie diese beiden Vorgänge ausführen, müssen Sie warten, bis die Warteschlange in einen nicht leeren Zustand übergeht oder die Aufgabe in den abgeschlossenen Zustand übergeht. Der einfachste Weg für uns, zustandsabhängige Klassen zu erstellen, besteht darin, sie auf der Grundlage einer Klassenbibliothek zu erstellen. Wenn die Klassenbibliothek jedoch nicht über die gewünschte Funktionalität verfügt, können Sie auch den zugrunde liegenden Mechanismus der Java-Sprache und der Klassenbibliothek verwenden, um Ihren eigenen Synchronisationsmechanismus zu erstellen.
In diesem Artikel erfahren Sie, wie Sie Ihre eigene Zustandsabhängigkeitsklasse erstellen. Führen Sie Schritt für Schritt von der einfachsten Konstruktion zur komplexen standardisierten Konstruktion ein, um den Prozess zu verstehen und das Endergebnis zu erzielen.
Blockierbare Zustandsabhängigkeitsoperationen werden im folgenden Pseudocode dargestellt:
acquire lock on object state //首先获取锁 while (precondition does not hold) { //前提条件是否满足,不满足则一直循环重试 release lock //释放锁 wait until precondition might hold //等待知道满足前提条件 optionally fail if interrupted or timeout expire //中断或者超时,各种异常 reacquire lock //重新获取锁 } perform action //执行任务 release lock //释放锁
Erlangen Sie die Sperre, prüfen Sie, ob die Bedingungen erfüllt sind, und geben Sie sie frei, wenn nicht Die Sperre wechselt in den Sperrzustand, bis die Bedingung erfüllt oder unterbrochen wird, eine Zeitüberschreitung auftritt usw. und die Sperre erneut erworben wird. Führen Sie die Aufgabe aus und geben Sie die Sperre frei.
Sie werden es vielleicht nicht intuitiv verstehen, wenn Sie sich diesen Pseudocode ansehen. Lesen Sie einfach weiter und Sie werden wissen, was es bedeutet, nachdem Sie diesen Artikel gelesen haben.
ArrayBlockingQueue ist ein begrenzter Cache, der zwei Operationen bereitstellt: Put und Take. Sie alle enthalten eine Voraussetzung: Elemente können nicht in einen vollen Cache gelegt werden und Elemente können nicht aus einem leeren Cache abgerufen werden. Nun, unser Ziel ist es, eine solche ArrayBlockingQueue zu erstellen.
Als nächstes werden zwei Implementierungen des begrenzten Caches vorgestellt, die unterschiedliche Methoden verwenden, um mit Situationen umzugehen, in denen Vorbedingungen nicht erfüllt sind.
Schauen wir uns zunächst die folgende Basisklasse BaseBoundeBuffer an. Nachfolgende Implementierungen erweitern diese Basisklasse. Es handelt sich um einen Array-basierten zirkulären Cache, und die darin enthaltenen Variablen buf, head, tail und count werden durch die integrierte Sperre des Caches geschützt. Es bietet auch synchrone doPut- und doTake-Methoden. In Unterklassen werden Put- und Take-Operationen über diese Methoden implementiert, und der zugrunde liegende Status wird vor Unterklassen verborgen.
public abstract class BaseBoundedBuffer<V> { private final V[] buf; private int tail; private int head; private int count; protected BaseBoundedBuffer(int capacity) { this.buf = (V[]) new Object[capacity]; count = 0; } protected synchronized final void doPut(V v) { buf[tail] = v; if(++tail == buf.length) tail = 0; ++count; } protected synchronized final V doTake() { V v = buf[head]; buf[head] = null; if(++head == buf.length) head = 0; --count; return v; } public synchronized final boolean isFull() { return count == buf.length; } public synchronized final boolean isEmpty() { return count == 0; } }
Die erste Implementierung des begrenzten Caches synchronisiert sowohl Put- als auch Take-Methoden, prüft zuerst und führt dann aus und löst eine Ausnahme aus, wenn dies fehlschlägt.
public class GrumpyBoundedBuffer<V> extends BaseBoundedBuffer{ protected GrumpyBoundedBuffer(int capacity) { super(capacity); } public synchronized void put(V v) throws BufferFullException { if(isFull()) { throw new BufferFullException(); } doPut(v); } public synchronized V take() throws BufferFullException { if(isEmpty()) throw new BufferFullException(); return (V) doTake(); } }
Wenn die Voraussetzungen nicht erfüllt sind, wird wie oben gezeigt direkt eine Ausnahme ausgelöst. Die sogenannte Ausnahme bezieht sich hier darauf, dass der Cache voll oder leer ist. Tatsächlich bedeutet diese Ausnahme nicht, dass das Programm falsch ist. Wenn Sie beispielsweise eine rote Ampel sehen, bedeutet dies nicht, dass die Ampel nicht normal ist, sondern dass Sie warten, bis die grüne Ampel die Straße überquert. Das bedeutet also, dass der Aufrufer die Ausnahme abfangen und jeden Cache-Vorgang wiederholen muss.
Sehen wir uns den folgenden Client-Aufrufcode direkt an:
private static GrumpyBoundedBuffer gbb = new GrumpyBoundedBuffer(5); ...while(true) { try { V item = gbb.take(); break; } catch(BufferEmptyException e) { Thread.sleep(500); } }
Um es ganz klar auszudrücken: Wenn die Vorbedingung nicht erfüllt ist, versuchen Sie es erneut, bis die Bedingung erfüllt ist, sodass es so aussieht, als ob die Blockierung möglich ist Wirkung erzielt werden. In diesem Fall muss der Aufrufer jedoch den Fehler der Vorbedingung selbst beheben und die CPU ist immer belegt. Das Problem hierbei ist, dass es für den Anrufer sehr mühsam sein wird, diese Warteschlange zu nutzen!
Die zweite Methode, SleepyBoundedBuffer, implementiert einen einfachen blockierenden Wiederholungsmechanismus durch Abfragen und Schlafen, sodass der Aufrufer den Wiederholungsmechanismus entfernen und die Verwendung des Caches vereinfachen kann. Bitte schauen Sie sich die folgende Codeauflistung an:
public class SleepyBoundedBuffer<V> extends BaseBoundedBuffer{ protected SleepyBoundedBuffer(int capacity) { super(capacity); // TODO Auto-generated constructor stub } public void put(V v) throws InterruptedException { while(true) { synchronized(this) { if(!isFull()) { doPut(v); return; } } Thread.sleep(200); } } public V take() throws InterruptedException{ while(true) { synchronized(this) { if(!isEmpty()) { return (V) doTake(); } } Thread.sleep(200); } } }
Aus Sicht des Aufrufers funktioniert diese Methode gut. Wenn eine Operation die Voraussetzungen erfüllt, wird sie sofort ausgeführt, andernfalls wird sie blockiert. Der Aufrufer muss keine Fehler und Wiederholungsversuche verarbeiten, er muss jedoch weiterhin InterruptedException verarbeiten. Wie die meisten gut funktionierenden Methoden der Blockierungsbibliothek unterstützt SleepyBoundedBuffer den Abbruch über Interrupts.
Das Problem bei SleepyBoundedBuffer ist, wie lange ist die Einstellung der Schlafzeit angemessen? Wie können wir optimale Leistung erzielen? Wie in der folgenden Abbildung dargestellt, setzt Thread B die Bedingung auf „true“, aber zu diesem Zeitpunkt schläft A noch. Dieser Ruhezustand ist der Leistungsengpass.
Gibt es eine Möglichkeit, dies zu erreichen? Wenn die Bedingung erfüllt ist, wird der Thread sofort aktiviert, um ihn auszuführen?
Probieren wir es einfach mal aus, ich erkläre es dir im nächsten Artikel!
Das obige ist der detaillierte Inhalt vonImplementierung einer Blockierungswarteschlange für die Verwaltung tatsächlicher Zustandsabhängigkeiten in der JAVA-Entwicklung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!