Als Tool zum gleichzeitigen Datenaustausch und zur Gewährleistung der Konsistenz verfügen Sperren über mehrere Implementierungen auf der JAVA-Plattform (z. B. synchronisiert und ReentrantLock usw.). Diese bereits geschriebenen Sperren erleichtern unsere Entwicklung, die spezifische Art und der Typ der Sperren werden jedoch selten erwähnt. In dieser Artikelserie werden gängige Sperrnamen und -merkmale unter JAVA analysiert, um Ihre Fragen zu beantworten.
1. Drehsperre
Spin-Sperren werden implementiert, indem dem aktuellen Thread die kontinuierliche Ausführung innerhalb des Schleifenkörpers ermöglicht wird. Nur wenn die Bedingungen der Schleife durch andere Threads geändert werden, kann der kritische Abschnitt betreten werden. Kopieren Sie den Code wie folgt:
öffentliche Klasse SpinLock {
private AtomicReference<Thread> sign =new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
while(!sign .compareAndSet(null, current)){
}
}
public void unlock (){
Thread current = Thread.currentThread();
sign .compareAndSet(current, null);
}
}
Mithilfe atomarer CAS-Operationen setzt die Sperrfunktion den Besitzer auf den aktuellen Thread und sagt voraus, dass der ursprüngliche Wert leer ist. Die Entsperrfunktion setzt den Besitzer auf null und der vorhergesagte Wert ist der aktuelle Thread.
Wenn ein zweiter Thread den Sperrvorgang aufruft, wird die Schleife ausgeführt, da der Besitzerwert nicht leer ist, bis der erste Thread die Entsperrfunktion aufruft, um den Besitzer auf Null zu setzen, und der zweite Thread den kritischen Abschnitt betreten kann.
Da die Spin-Sperre nur dafür sorgt, dass der aktuelle Thread den Schleifenkörper ausführt, ohne den Thread-Status zu ändern, ist die Reaktionsgeschwindigkeit schneller. Wenn die Anzahl der Threads jedoch weiter zunimmt, sinkt die Leistung erheblich, da jeder Thread ausgeführt werden muss und CPU-Zeit beansprucht. Wenn der Thread-Wettbewerb nicht intensiv ist und die Sperre für einen bestimmten Zeitraum aufrechterhalten wird. Geeignet für die Verwendung mit Drehschlössern.
Hinweis: In diesem Beispiel handelt es sich um eine unfaire Sperre. Die Reihenfolge der Erlangung der Sperre richtet sich nicht nach der Reihenfolge, in der die Sperre eingegeben wird.
2. Andere Arten von Spin-Locks
Wir haben oben über Spin-Locks gesprochen. Es gibt drei gängige Lock-Formen bei Spin-Locks: TicketLock, CLHlock und MCSlock.
Die Ticketsperre löst hauptsächlich das Problem der Zugriffssequenz. Das Hauptproblem liegt bei Multi-Core-CPUs:
Kopieren Sie den Codecode wie folgt:
Paket com.alipay.titan.dcc.dal.entity;
import java.util.concurrent.atomic.AtomicInteger;
öffentliche Klasse TicketLock {
private AtomicInteger serviceNum = new AtomicInteger();
private AtomicInteger ticketNum = new AtomicInteger();
private static final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();
public void lock() {
int myticket = ticketNum.getAndIncrement();
LOCAL.set(myticket);
while (myticket != serviceNum.get()) {
}
}
public void unlock() {
int myticket = LOCAL.get();
serviceNum.compareAndSet(myticket, myticket + 1);
}
}
Eine serviceNum-Dienstnummer muss jedes Mal abgefragt werden, was sich auf die Leistung auswirkt (sie muss aus dem Hauptspeicher gelesen werden und andere CPUs müssen daran gehindert werden, sie zu ändern).
CLHLock und MCSLock sind zwei ähnliche Arten fairer Sperren, sortiert in Form einer verknüpften Liste.
Kopieren Sie den Codecode wie folgt:
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
öffentliche Klasse CLHLock {
öffentliche statische Klasse CLHNode {
privater flüchtiger boolescher Wert isLocked = true;
}
@SuppressWarnings("unused")
privater flüchtiger CLHNode-Ende;
private static final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
CLHNode.class, „tail“);
public void lock() {
CLHNode node = new CLHNode();
LOCAL.set(node);
CLHNode preNode = UPDATER.getAndSet(this, node);
if (preNode != null) {
while (preNode.isLocked) {
}
preNode = null;
LOCAL.set(node);
}
}
public void unlock() {
CLHNode node = LOCAL.get();
if (!UPDATER.compareAndSet(this, node, null)) {
node.isLocked = false;
}
Knoten = null;
}
}
CLHlock fragt kontinuierlich Vorläufervariablen ab, was es für die Verwendung unter der NUMA-Architektur ungeeignet macht (in dieser Architektur ist jeder Thread in einem anderen physischen Speicherbereich verteilt).
MCSLock durchläuft die Knoten lokaler Variablen. Mit CLHlock gibt es kein Problem.
Kopieren Sie den Codecode wie folgt:
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
öffentliche Klasse MCSLock {
öffentliche statische Klasse MCSNode {
flüchtiger MCSNode als nächstes;
volatile boolean isLocked = true;
}
private static final ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
@SuppressWarnings("unused")
private flüchtige MCSNode-Warteschlange;
privater statischer finaler AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
MCSNode.class, „Warteschlange“);
public void lock() {
MCSNode currentNode = new MCSNode();
NODE.set(currentNode);
MCSNode preNode = UPDATER.getAndSet(this, currentNode);
if (preNode != null) {
preNode.next = currentNode;
while (currentNode.isLocked) {
}
}
}
public void unlock() {
MCSNode currentNode = NODE.get();
if (currentNode.next == null) {
if (UPDATER.compareAndSet(this, currentNode, null)) {
} anders {
while (currentNode.next == null) {
}
}
} anders {
currentNode.next.isLocked = false;
currentNode.next = null;
}
}
}
Aus Code-Sicht ist CLH einfacher als MCS.
Die CLH-Warteschlange ist eine implizite Warteschlange und verfügt über keine echten Nachfolgerknotenattribute.
Die MCS-Warteschlange ist eine explizite Warteschlange mit echten Nachfolgerknotenattributen.
Die von JUC ReentrantLock intern verwendete Standardsperre ist die CLH-Sperre (es gibt viele Verbesserungen, z. B. das Ersetzen von Spin-Sperren durch Blockierungssperren usw.).
(Volltext endet)