Heim  >  Artikel  >  Java  >  Was sind die Implementierungsmethoden zum Sperren in Java?

Was sind die Implementierungsmethoden zum Sperren in Java?

WBOY
WBOYnach vorne
2023-05-12 08:37:051606Durchsuche

1. Pessimistische Sperre

Wie der Name schon sagt, bezieht es sich auf eine konservative Haltung gegenüber Datenänderungen und den Glauben, dass auch andere die Daten ändern werden. Daher werden beim Betrieb von Daten die Daten gesperrt, bis der Vorgang abgeschlossen ist. In den meisten Fällen beruht die pessimistische Sperre auf dem Sperrmechanismus der Datenbank, um maximale Exklusivität der Vorgänge sicherzustellen. Wenn die Sperrzeit zu lang ist, können andere Benutzer längere Zeit nicht darauf zugreifen, was sich auf den gleichzeitigen Zugriff des Programms auswirkt. Dies hat insbesondere auch große Auswirkungen auf die Datenbankleistung Bei langen Transaktionen ist dieser Overhead oft unerträglich.

Wenn es sich um ein eigenständiges System handelt, können wir das mit JAVA gelieferte synchronisierte Schlüsselwort verwenden, um die Ressourcen zu sperren, indem wir es der Methode oder dem synchronisierten Block hinzufügen. Wenn es sich um ein verteiltes System handelt, können wir dies tun Verwenden Sie dazu den Sperrmechanismus der Datenbank selbst.

select * from 表名 where id= #{id} for update

Bei der Verwendung pessimistischer Sperren müssen wir auf die Sperrstufe achten. Wenn MySQL innodb sperrt, wird nur der Primärschlüssel oder (Indexfeld) explizit angegeben, um die Zeilensperre zu verwenden Wenn Sie eine Sperre ausführen, wird die gesamte Tabelle gesperrt, und die Leistung wird sehr schlecht sein. Wenn wir pessimistisches Sperren verwenden, müssen wir das Autocommit-Attribut der MySQL-Datenbank deaktivieren, da MySQL standardmäßig den Autocommit-Modus verwendet. Pessimistisches Sperren eignet sich für Szenarien mit vielen Schreibvorgängen und erfordert keine hohe Parallelitätsleistung.

2. Optimistische Sperre

Optimistische Sperre, Sie können es wahrscheinlich anhand der wörtlichen Bedeutung erraten, wenn Sie Daten verarbeiten, da Sie davon ausgehen, dass andere die Daten nicht ändern werden Gleichzeitig ist es optimistisch, dass die Sperre nicht gesperrt wird. Erst wenn das Update eingereicht wird, wird der Datenkonflikt offiziell erkannt. Wenn ein Konflikt festgestellt wird, wird eine Fehlermeldung zurückgegeben und der Benutzer kann entscheiden, was zu tun ist, Fail-Fast-Mechanismus. Andernfalls führen Sie diesen Vorgang aus.

ist in drei Phasen unterteilt: Datenlesen, Schreibüberprüfung und Datenschreiben.

Wenn es sich um ein eigenständiges System handelt, können wir es basierend auf JAVA implementieren. CAS ist eine atomare Operation, die mithilfe von Hardwarevergleich und -austausch implementiert wird.

Wenn es sich um ein verteiltes System handelt, können wir der Datenbanktabelle ein Versionsnummernfeld hinzufügen, z. B. Version.

update 表 
set ... , version = version +1 
where id= #{id} and version = #{version}

Lesen Sie vor dem Betrieb die Versionsnummer des Datensatzes. Vergleichen Sie beim Aktualisieren die Versionsnummern mithilfe von SQL-Anweisungen, um festzustellen, ob sie konsistent sind. Wenn konsistent, aktualisieren Sie die Daten. Andernfalls wird die Version erneut gelesen und der obige Vorgang erneut versucht.

3, verteilte Sperren

synchronized, ReentrantLock usw. in JAVA lösen alle das Problem des gegenseitigen Ausschlusses von Ressourcen bei der Bereitstellung einzelner Anwendungen auf einer Maschine. Wenn sich mit der schnellen Geschäftsentwicklung eine einzelne Anwendung zu einem verteilten Cluster entwickelt, werden Multi-Threads und Multi-Prozesse auf verschiedene Maschinen verteilt und die ursprüngliche Sperrstrategie für die Parallelitätskontrolle auf einer Maschine wird ungültig

Zu diesem Zeitpunkt müssen wir verteilte Sperren einführen, um den maschinenübergreifenden gegenseitigen Ausschlussmechanismus zu lösen und den Zugriff auf gemeinsam genutzte Ressourcen zu steuern.

Welche Bedingungen sind für verteilte Sperren erforderlich:

  • Die gleiche gegenseitige Ausschlussfunktion für Ressourcen wie ein eigenständiges System, das die Grundlage bildet die Sperre#🎜🎜 #

  • Hochleistung beim Erfassen und Freigeben von Sperren

  • Hohe Verfügbarkeit

  • # 🎜🎜#Mit Wiedereintritt
  • Verfügt über einen Sperrfehlermechanismus, um Deadlocks zu verhindern -blocking Unabhängig davon, ob die Sperre erhalten wird, muss sie schnell zurückgegeben werden können
  • Es gibt viele Implementierungsmethoden, die auf Datenbanken, Redis und Zookeeper usw. basieren . Hier sprechen wir über die gängige Redis-basierte Implementierungsmethode:
  • Lock

    SET key unique_value  [EX seconds] [PX milliseconds] [NX|XX]
  • Wenn die Ausführung erfolgreich ist und 1 zurückgegeben wird, bedeutet dies, dass die Sperre erfolgt ist erfolgreich. Hinweis: „unique_value“ ist eine vom Client generierte eindeutige Kennung, um Sperrvorgänge von verschiedenen Clients zu unterscheiden. Achten Sie zunächst darauf, ob „unique_value“ ein gesperrter Client ist. Schließlich können wir von anderen Clients hinzugefügte Sperren nicht löschen.

Entsperren: Das Entsperren verfügt über zwei Befehlsoperationen, die die Verwendung von Lua-Skripten erfordern, um die Atomizität sicherzustellen.

// 先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

Mit der hohen Leistung von Redis implementiert Redis verteilte Sperren, was auch die aktuelle Mainstream-Implementierungsmethode ist. Aber alles hat Vor- und Nachteile. Wenn der gesperrte Server ausfällt und der Slave-Knoten keine Zeit hatte, die Daten zu sichern, können auch andere Clients die Sperre erhalten.

Um dieses Problem zu lösen, hat Redis offiziell eine verteilte Sperre Redlock entwickelt.

Grundidee: Lassen Sie den Client parallele Anfragen zur Beantragung von Sperren mit mehreren unabhängigen Redis-Knoten stellen. Wenn der Sperrvorgang auf mehr als der Hälfte der Knoten erfolgreich abgeschlossen werden kann, gehen wir davon aus, dass der Client dies getan hat Erhalten Sie erfolgreich die verteilte Sperre, andernfalls schlägt die Sperre fehl.

4. Wiedereintrittssperre

Wiedereintrittssperre, auch rekursive Sperre genannt, bedeutet, dass derselbe Thread die äußere Methode aufrufen kann, um die Sperre zu erhalten Die Methode erhält die Sperre automatisch.

Es gibt einen Zähler innerhalb der Objektsperre oder Klassensperre. Jedes Mal, wenn ein Thread die Sperre erhält, beträgt der Zähler +1, wenn er entsperrt wird.

Die Anzahl der Sperren entspricht der Anzahl der Entriegelungen. Sperren und Entriegelungen erscheinen paarweise.

ReentrantLock und synchronisiert in Java sind beide Wiedereintrittssperren. Ein Vorteil von Wiedereintrittssperren besteht darin, dass Deadlocks bis zu einem gewissen Grad vermieden werden können.

5、自旋锁

自旋锁是采用让当前线程不停地在循环体内执行,当循环的条件被其他线程改变时才能进入临界区。自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不断增加时,性能下降明显,因为每个线程都需要执行,会占用CPU时间片。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

自旋锁缺点:

  • 可能引发死锁。

  • 可能占用 CPU 的时间过长。

我们可以设置一个 循环时间 或 循环次数,超出阈值时,让线程进入阻塞状态,防止线程长时间占用 CPU 资源。JUC 并发包中的 CAS 就是采用自旋锁,compareAndSet 是CAS操作的核心,底层利用Unsafe对象实现的。

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

如果内存中 var1 对象的var2字段值等于预期的 var5,则将该位置更新为新值(var5 + var4),否则不进行任何操作,一直重试,直到操作成功为止。

CAS 包含了Compare和Swap 两个操作,如何保证原子性呢?CAS 是由 CPU 支持的原子操作,其原子性是在硬件层面进行控制。

特别注意,CAS 可能导致 ABA 问题,我们可以引入递增版本号来解决。

6、独享锁

独享锁,也有人叫它排他锁。无论读操作还是写操作,只能有一个线程获得锁,其他线程处于阻塞状态。

缺点:读操作并不会修改数据,而且大部分的系统都是 读多写少,如果读读之间互斥,大大降低系统的性能。下面的 共享锁 会解决这个问题。

像Java中的 ReentrantLock 和 synchronized 都是独享锁。

7、共享锁

共享锁是指允许多个线程同时持有锁,一般用在读锁上。读锁的共享锁可保证并发读是非常高效的。读写,写读 ,写写的则是互斥的。独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

ReentrantReadWriteLock,其读锁是共享锁,其写锁是独享锁。

8、读锁/写锁

如果对某个资源是读操作,那多个线程之间并不会相互影响,可以通过添加读锁实现共享。如果有修改动作,为了保证数据的并发安全,此时只能有一个线程获得锁,我们称之为 写锁。读读是共享的;而 读写、写读 、写写 则是互斥的。

像 Java中的 ReentrantReadWriteLock 就是一种 读写锁。

9、公平锁/非公平锁

公平锁:多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,先来先获取的公平性原则。

优点:所有的线程都能得到资源,不会饿死在队列中。

缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,CPU 唤醒下一个阻塞线程有系统开销。

Was sind die Implementierungsmethoden zum Sperren in Java?

非公平锁:多个线程不按照申请锁的顺序去获得锁,而是同时以插队方式直接尝试获取锁,获取不到(插队失败),会进入队列等待(失败则乖乖排队),如果能获取到(插队成功),就直接获取到锁。

优点:可以减少 CPU 唤醒线程的开销,整体的吞吐效率会高点。

缺点:可能导致队列中排队的线程一直获取不到锁或者长时间获取不到锁,活活饿死。

Java 多线程并发操作,我们操作锁大多时候都是基于 Sync 本身去实现的,而 Sync 本身却是 ReentrantLock 的一个内部类,Sync 继承 AbstractQueuedSynchronizer。

像 ReentrantLock 默认是非公平锁,我们可以在构造函数中传入 true,来创建公平锁。

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

10、可中断锁/不可中断锁

可中断锁:指一个线程因为没有获得锁在阻塞等待过程中,可以中断自己阻塞的状态。不可中断锁:恰恰相反,如果锁被其他线程获取后,当前线程只能阻塞等待。如果持有锁的线程一直不释放锁,那其他想获取锁的线程就会一直阻塞。

内置锁 synchronized 是不可中断锁,而 ReentrantLock 是可中断锁。

ReentrantLock获取锁定有三种方式:

  • lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于阻塞状态,直到该线程获取锁。

  • tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false。

  • tryLock(langes Timeout,TimeUnit-Einheit), wenn die Sperre erhalten wird, wird sofort true zurückgegeben. Wenn ein anderer Thread die Sperre hält, wartet er auf die durch die angegebene Zeit Parameter: Wenn die Sperre während des Wartevorgangs erworben wird, wird „true“ zurückgegeben. Wenn die Wartezeit abläuft, wird „false“ zurückgegeben.

  • lockInterruptably(): Wenn die Sperre erworben wird, wird sie sofort zurückgegeben. Wenn die Sperre nicht erworben wird, wird der Thread blockiert, bis die Sperre erworben wird oder der Thread aktiviert wird unterbrochen durch einen anderen Thread.

11. Segmentierte Sperre

Segmentierte Sperre ist eigentlich ein Sperrdesign, der Zweck besteht darin, die Granularität der Sperre zu verfeinern, nicht A spezifisch Art der Sperre: Für ConcurrentHashMap besteht die Parallelitätsimplementierung darin, effiziente gleichzeitige Vorgänge in Form segmentierter Sperren zu erreichen.

Die Segmentsperre in ConcurrentHashMap heißt Segment und hat eine ähnliche Struktur wie HashMap (die Implementierung von HashMap in JDK7), das heißt, sie verfügt intern über ein Eintragsarray und jedes Element im Array ist eine verknüpfte Liste. ;Gleichzeitig ist es ein ReentrantLock (Segment erbt ReentrantLock).

Wenn Sie ein Element einfügen müssen, sperrt es nicht die gesamte HashMap, sondern erkennt zunächst über den Hashcode, in welches Segment es eingefügt werden soll, und sperrt dann dieses Segment, also beim Multithreading Beim Einfügen , paralleles Einfügen wird unterstützt, solange sie nicht im selben Segment platziert sind.

12. Sperren-Upgrade (keine Sperre|voreingenommene Sperre|leichte Sperre|schwere Sperre)

Vor JDK 1.6 war synchronisiert noch eine schwere Sperre mit relativ geringer Effizienz. Nach JDK 1.6 optimierte die JVM jedoch die Synchronisierung, um die Effizienz der Sperrenerfassung und -freigabe zu verbessern, und führte voreingenommene Sperren und leichte Sperren ein. Von da an gibt es vier Sperrzustände: keine Sperre, voreingenommene Sperre und leichte Sperre , schweres Schloss. Diese vier Staaten werden mit dem Wettbewerb allmählich eskalieren und können nicht herabgestuft werden.

Was sind die Implementierungsmethoden zum Sperren in Java?

无码

Alle Threads können auf dieselbe Ressource zugreifen und diese ändern Ein Thread kann es gleichzeitig erfolgreich ändern. Dies wird oft als optimistisches Sperren bezeichnet.

voreingenommene Sperre

ist auf den ersten Thread ausgerichtet, der auf die Sperre zugreift. Wenn der synchronisierte Codeblock zum ersten Mal ausgeführt wird, wird das Sperrflag im Objektheader geändert Durch CAS wird das Sperrobjekt zur Bias-Sperre.

Wenn ein Thread auf einen synchronisierten Codeblock zugreift und eine Sperre erhält, wird die Thread-ID der Sperrvoreingenommenheit in Mark Word gespeichert. Wenn der Thread den synchronisierten Block betritt und verlässt, wird er nicht mehr über CAS-Operationen gesperrt und entsperrt, sondern erkennt, ob Mark Word eine Bias-Sperre speichert, die auf den aktuellen Thread verweist. Der Erwerb und die Freigabe leichter Sperren basieren auf mehreren atomaren CAS-Anweisungen, während voreingenommene Sperren beim Ersetzen von ThreadID nur auf einer atomaren CAS-Anweisung basieren müssen.

Nach der Ausführung des Synchronisationscodeblocks gibt der Thread die Bias-Sperre nicht aktiv frei. Wenn der Thread den Synchronisationscodeblock zum zweiten Mal ausführt, bestimmt der Thread, ob der Thread, der die Sperre zu diesem Zeitpunkt hält, er selbst ist (die ID des Threads, der die Sperre hält, befindet sich auch im Objektheader), und wenn ja, er selbst wird normal ausgeführt. Da die Sperre noch nicht freigegeben wurde, ist eine erneute Sperre hier nicht erforderlich. Die voreingenommene Sperre verursacht nahezu keinen zusätzlichen Aufwand und weist eine extrem hohe Leistung auf.

Bias-Sperre Nur wenn andere Threads versuchen, um die Bias-Sperre zu konkurrieren, gibt der Thread, der die Bias-Sperre hält, die Sperre frei. Der Thread wird die Bias-Sperre nicht aktiv aufheben. In Bezug auf den Widerruf der voreingenommenen Sperre müssen Sie auf den globalen Sicherheitspunkt warten. Wenn zu einem bestimmten Zeitpunkt kein Bytecode ausgeführt wird, wird zunächst der Thread angehalten, der die voreingenommene Sperre besitzt, und dann festgestellt, ob Das Sperrobjekt ist gesperrt. Wenn der Thread nicht aktiv ist, wird der Objektheader auf einen sperrenfreien Zustand gesetzt und die voreingenommene Sperre wird widerrufen, wodurch in den sperrenfreien Zustand (Flag-Bit ist 01) oder den leichten Sperrzustand (Flag-Bit ist 00) zurückgekehrt wird.

Voreingenommenes Sperren bedeutet, dass der Thread bei nachfolgenden Zugriffen automatisch die Sperre erhält, wenn immer von demselben Thread auf einen synchronisierten Code zugegriffen wird, d. h. wenn es keine Konkurrenz zwischen mehreren Threads gibt. Dadurch werden die Kosten für die Anschaffung eines Schlosses gesenkt.

Lightweight-Sperre

Die aktuelle Sperre ist eine Bias-Sperre. Wenn mehrere Threads gleichzeitig um die Sperre konkurrieren, wird die Bias-Sperre zu einer Lightweight-Sperre aufgewertet. Leichte Schlösser gehen davon aus, dass zwar Wettbewerb besteht, dieser jedoch im Idealfall sehr gering ist und das Schloss durch Spin erworben wird.

Es gibt zwei Situationen, um leichte Schlösser zu erhalten:

  • Wenn die Bias-Lock-Funktion ausgeschaltet ist.

  • Mehrere Threads, die um die Bias-Sperre konkurrieren, führen dazu, dass die Bias-Sperre auf eine Lightweight-Sperre aktualisiert wird. Sobald ein zweiter Thread dem Sperrenwettbewerb beitritt, wird die voreingenommene Sperre zu einer leichten Sperre (Spin-Sperre) aufgewertet.

Setzen Sie den Sperrenwettbewerb im Lightweight-Sperrzustand fort. Threads, die die Sperre nicht ergriffen haben, werden gedreht und kontinuierlich wiederholt, um festzustellen, ob die Sperre erfolgreich erworben werden kann. Der Vorgang zum Erlangen der Sperre besteht tatsächlich darin, das Sperrflag im Objektheader über CAS zu ändern. Vergleichen Sie zunächst, ob das aktuelle Sperrflag „freigegeben“ ist, und setzen Sie es in diesem Fall auf „gesperrt“. Wenn die Sperre erfasst wird, ändert der Thread die aktuellen Informationen zum Sperreninhaber in sich selbst.

重量级锁

如果线程的竞争很激励,线程的自旋超过了一定次数(默认循环10次,可以通过虚拟机参数更改),将轻量级锁升级为重量级锁(依然是 CAS  修改锁标志位,但不修改持有锁的线程ID),当后续线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起(而不是忙等),等待将来被唤醒。

重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。简言之,就是所有的控制权都交给了操作系统,由操作系统来负责线程间的调度和线程的状态变更。而这样会出现频繁地对线程运行状态的切换,线程的挂起和唤醒,从而消耗大量的系统资。

13、锁优化技术(锁粗化、锁消除)

锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

举个例子:有个循环体,内部。

for(int i=0;i<size;i++){
    synchronized(lock){
        ...业务处理,省略
    }
}

经过锁粗化的代码如下:

synchronized(lock){
    for(int i=0;i<size;i++){
        ...业务处理,省略
    }
}

锁消除指的是在某些情况下,JVM 虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而到底提高程序性能的目的。

锁消除的依据是逃逸分析的数据支持,如 StringBuffer 的 append() 方法,或 Vector 的 add() 方法,在很多情况下是可以进行锁消除的,比如以下这段代码:

public String method() {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 10; i++) {
        sb.append("i:" + i);
    }
    return sb.toString();
}

以上代码经过编译之后的字节码如下:

Was sind die Implementierungsmethoden zum Sperren in Java?

从上述结果可以看出,之前我们写的线程安全的加锁的 StringBuffer 对象,在生成字节码之后就被替换成了不加锁不安全的 StringBuilder 对象了,原因是 StringBuffer 的变量属于一个局部变量,并且不会从该方法中逃逸出去,所以我们可以使用锁消除(不加锁)来加速程序的运行。

Das obige ist der detaillierte Inhalt vonWas sind die Implementierungsmethoden zum Sperren in Java?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen