[Gleichzeitige Programmierung] Synchronisiert löst die Analyse gemeinsamer Variablen

Der Hauptinhalt dieses Artikels:
Durch gemeinsam genutzte Variablen verursachte Probleme
So verwenden Sie Synchronized, um die Inkonsistenz von Lese- und Schreibdaten zu lösen, die durch verursacht wird gemeinsam genutzte Variablen

1. Nachdenken über die Probleme, die durch das Teilen entstehen

1.1 Die Verkörperung gemeinsam genutzter Variablen in Java

Überlegung: Zwei Threads führen jeweils 5000 Mal eine Inkrementierung und Dekrementierung einer statischen Variablen mit einem Anfangswert von 0 durch. Ist das Ergebnis 0?

static int counter = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter--;
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }

1.2 Problemanalyse

Das obige Ergebnis kann eine positive Zahl, eine negative Zahl oder Null sein. Warum? Da das Inkrementieren und Dekrementieren statischer Variablen in Java keine atomaren Operationen sind, müssen Sie es zum vollständigen Verständnis anhand des Bytecodes analysieren.
Beispielsweise für i++ (i ist eine statische Variable). Die folgenden JVM-Bytecode-Anweisungen werden tatsächlich generiert:

getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
iadd // 自增
putstatic i // 将修改后的值存入静态变量i

Das entsprechende i-- ist ähnlich:

getstatic i // 获取静态变量i的值
iconst_1 // 准备常量1
isub // 自减
putstatic i // 将修改后的值存入静态变量i

Schauen wir uns dann das Speichermodell von Java an und erfahren Sie, wie Sie die Selbstinkrementierungs- und Selbstdekrementierungsoperationen daran erinnern können:
Fügen Sie hier eine Bildbeschreibung ein

Wenn es sich um einen einzelnen Thread handelt, werden die 8 Codezeilen nacheinander (nicht verschachtelt) ausgeführt, kein Problem
Fügen Sie hier eine Bildbeschreibung ein
Bei Multithreading sind diese 8 Zeilen jedoch Der Code kann verschachtelt sein.< a i=2> Zum Beispiel:

Fügen Sie hier eine Bildbeschreibung ein

1.3 Das Konzept des kritischen Abschnitts

  • Es gibt kein Problem, wenn ein Programm mehrere Threads ausführt
  • Das Problem liegt darin, dass mehrere Threads aufgemeinsame Ressourcen zugreifen
    Mehrere Threads lesengemeinsame Ressourcen gemeinsamen Ressourcen
    Die Anweisung tritt auf, wenn mehrere Threads Lese- und Schreibvorgänge aufTatsächlich gibt es kein Problem
  • Wenn es innerhalb eines Codeblocks Multithread-Lese- und Schreibvorgänge aufgemeinsam genutzten Ressourcen gibt, wird dieser Codeblock aufgerufen < a i=3>kritischer Abschnitt

Zum Beispiel der kritische Abschnitt im folgenden Code

static int counter = 0;
    static void increment()
    // 临界区
    {
        counter++;
    }
    static void decrement()
    // 临界区
    {
        counter--;
    }

2. Synchronisierte Lösung

Um Rennbedingungen in kritischen Abschnitten zu vermeiden, gibt es viele Möglichkeiten, das Ziel zu erreichen.

  • Blockierungslösungen: synchronisiert, Sperre
  • Nicht blockierende Lösung: atomare Variablen

Dieser Artikel verwendet eine Blockierungslösung: synchronisiert, um das oben genannte Problem zu lösen, allgemein bekannt als [Objektsperre]. Es verwendet eine gegenseitige Ausschlussmethode, sodass höchstens ein Thread gleichzeitig [Objektsperre] halten kann und andere Threads denken können darüber. Es wird blockiert, wenn diese [Objektsperre] erworben wird. Dadurch wird sichergestellt, dass der Thread, der die Sperre besitzt, den Code im kritischen Abschnitt sicher ausführen kann, ohne sich Gedanken über den Thread-Kontextwechsel machen zu müssen.

Hinweis
Obwohl sowohl gegenseitiger Ausschluss als auch Synchronisierung in Java mit dem synchronisierten Schlüsselwort erreicht werden können, sind sie dennoch unterschiedlich:
Gegenseitiger Ausschluss ist sicherzustellen Da im kritischen Abschnitt Race Conditions auftreten, kann nur ein Thread gleichzeitig den kritischen Abschnittscode ausführen
Die Synchronisierung erfolgt aufgrund der unterschiedlichen Ausführungsreihenfolge und Reihenfolge der Threads, wodurch ein Thread warten muss damit andere Threads bis zu einem bestimmten Punkt laufen

2.1 synchronisierte Syntax

synchronized(对象) // 线程1, 线程2(blocked)
{
 临界区
}

synchronisiert auf Methode

class Test{
        public synchronized void test() {

        }
    }
    等价于
    class Test{
        public void test() {
            synchronized(this) {

            }
        }
    }
class Test{
        public synchronized static void test() {
        }
    }
    等价于
    class Test{
        public static void test() {
            synchronized(Test.class) {

            }
        }
    }

2.2 Lösung

static int counter = 0;
    static final Object room = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }

Wir können es so verstehen:

  • Das synchronisierte Objekt (Objekt) kann man sich als Raum (Raum) vorstellen. Es gibt einen eindeutigen Eingang (Tür). Zu Berechnungszwecken kann jeweils nur eine Person den Raum betreten. Die Threads t1 und t2 kann man sich als zwei Personen vorstellen.
  • Wenn Thread t1 synchronisiert (Raum) ausführt, ist es so, als ob t1 ​​den Raum betritt, die Tür abschließt, den Schlüssel wegnimmt und den count++-Code in der Tür ausführt.
  • Wenn t2 zu diesem Zeitpunkt auch synchronisiert (Raum) ausgeführt wird, stellt es fest, dass die Tür verriegelt ist und nur außerhalb der Tür warten kann. Ein Kontextwechsel erfolgt und wird blockiert.
  • Auch wenn die CPU-Zeitscheibe von t1 leider abläuft und aus der Tür geworfen wird (verstehen Sie nicht, dass das Sperren des Objekts weiterhin ausgeführt werden kann), ist die Tür zu diesem Zeitpunkt immer noch verschlossen, t1 hält immer noch den Schlüssel und t2 Der Thread ist immer noch blockiert und kann nicht eintreten. Du kannst die Tür nur öffnen und eintreten, wenn t1 das nächste Mal an der Reihe ist und du erhältst erneut die Zeitscheibe.
  • Wenn t1 mit der Ausführung des Codes im synchronisierten {}-Block fertig ist, kommt er aus dem Raum von obj und entriegelt die Tür, weckt den t2-Thread auf und gibt ihm den Schlüssel. Erst dann kann der T2-Thread den Obj-Raum betreten, die Tür abschließen, den Schlüssel nehmen und seinen Zählcode ausführen

Stellen Sie mit Diagrammen dar
Fügen Sie hier eine Bildbeschreibung ein

Der Artikel wird ständig aktualisiert. Sie können dem öffentlichen Konto unten folgen oder auf WeChat nach „Last Rosemary“ suchen, um den Quellcode des Projekts zu erhalten, und ihn sofort lesen, um umfassendere Linkinformationen zu erhalten.

Supongo que te gusta

Origin blog.csdn.net/qq_38374397/article/details/134683610
Recomendado
Clasificación