Heim  >  Artikel  >  Java  >  So lösen Sie die Neuordnung von Java-Anweisungen in einer Multithread-Umgebung

So lösen Sie die Neuordnung von Java-Anweisungen in einer Multithread-Umgebung

PHPz
PHPznach vorne
2023-04-19 15:40:061261Durchsuche

1. Vorwort

Die Neuanordnung von Anweisungen trägt dazu bei, die Ausführungseffizienz des Programms in einer Single-Thread-Umgebung zu verbessern, und hat keine negativen Auswirkungen auf das Programm; in einer Multi-Thread-Umgebung führt die Neuanordnung von Anweisungen zu unerwarteten Fehlern im Programm .

2. Problembehebung

(1) Zugehörige Variablen

Das Folgende ist ein Beispiel, mit dem die Neuanordnung von Anweisungen zu 100 % wiederhergestellt werden kann.

public class D {
    static Integer a;
    static Boolean flag;
    
    public static void writer() {
        a = 1;
        flag = true;
    }
    
    public static void reader() {
        if (flag != null && flag) {
            System.out.println(a);
            a = 0;
            flag = false;
        }
    }
}
1. Ergebnisvorhersage

Die Methode reader gibt den Wert der Variablen a nur dann auf der Konsole aus, wenn die Variable flag wahr ist . reader方法仅在flag变量为true时向控制台打印变量a的值。

writer方法先执行变量a的赋值操作,后执行变量flag的赋值操作。

如果按照上述分析逻辑,那么控制台打印的结果一定全为1。

2、指令重排

假如代码未发生指令重排,那么当flag变量为true时,变量a一定为1。

上述代码中关于变量a和变量flag在两个方法类均存在指令重排的情况。

public static void writer() {
    a = 1;
    flag = true;
}

通过观察日志输出,发现有大量的0输出。

writer方法内部发生指令重排时,flag变量先完成赋值,此时假如当前线程发生中断,其它线程在调用reader方法,检测到flag变量为true,那么便打印变量a的值。此时控制台存在超出期望值的结果。

(二)new创建对象

使用关键字new创建对象时,因其非原子操作,故存在指令重排,指令重排在多线程环境下会带来负面影响。

public class Singleton {
    private static UserModel instance;
    
    public static UserModel getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new UserModel(2, "B");
                }
            }
        }
        return instance;
    }
}

@Data
@AllArgsConstructor
class UserModel {
    private Integer userId;
    private String userName;
}
1、解析创建过程
  • 使用关键字new创建一个对象,大致分为一下过程:

  • 在栈空间创建引用地址

  • 以类文件为模版在堆空间对象分配内存

  • 成员变量初始化

  • 使用构造函数初始化

  • 将引用值赋值给左侧存储变量

2、重排序过程分析

针对上述示例,假设第一个线程进入synchronized代码块,并开始创建对象,由于重排序存在,正常的创建对象过程被打乱,可能会出现在栈空间创建引用地址后,将引用值赋值给左侧存储变量,随后因CPU调度时间片耗尽而产生中断的情况。

后续线程在检测到instance

Die writer-Methode führt zuerst die Zuweisungsoperation der Variablen a und dann die Zuweisungsoperation der Variablen flag durch.

Wenn Sie der obigen Analyselogik folgen, müssen die von der Konsole gedruckten Ergebnisse alle 1 sein.

2. Befehlsumordnung

Wenn der Code keine Befehlsumordnung hat, muss die Variable a 1 sein, wenn die Variable flag wahr ist.

Im obigen Code gibt es in beiden Methodenklassen eine Befehlsumordnung bezüglich der Variablen a und der Variablen flag.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ValueModel {
    private Integer value;
    private Boolean flag;
}

Beim Beobachten der Protokollausgabe haben wir festgestellt, dass es eine große Anzahl von 0-Ausgaben gibt.

Wenn die Anweisungen innerhalb der writer-Methode neu angeordnet werden, wird zuerst die Variable flag zugewiesen. Wenn der aktuelle Thread zu diesem Zeitpunkt unterbrochen wird, rufen andere Threads auf Die Methode „reader code>“ erkennt, dass die Variable <code>flag wahr ist, und gibt dann den Wert der Variablen a aus. Zu diesem Zeitpunkt gibt es in der Konsole Ergebnisse, die den erwarteten Wert überschreiten.

(2) Neu erstellt Objekte

Bei Verwendung des Schlüsselworts new zum Erstellen eines Objekts kommt es aufgrund der nicht-atomaren Operation zu einer Neuanordnung der Anweisungen, die in einer Multithread-Umgebung negative Auswirkungen hat.

public class E {
    private static final AtomicReference<ValueModel> ar = new AtomicReference<>(new ValueModel());
    
    public static void writer() {
        ar.set(new ValueModel(1, true));
    }
    
    public static void reader() {
        ValueModel valueModel = ar.get();
        if (valueModel.getFlag() != null && valueModel.getFlag()) {
            System.out.println(valueModel.getValue());
            ar.set(new ValueModel(0, false));
        }
    }
}

1. Analysieren Sie den Erstellungsprozess

Verwenden Sie das Schlüsselwort new, um ein Objekt zu erstellen, das grob in die folgenden Prozesse unterteilt ist:

  • Erstellen Sie eine Referenzadresse im Stapelbereich

  • Verwenden Sie die Klasse Datei als Vorlage zum Zuweisen von Objekten im Heap-Speicherplatz. Initialisierung der Mitgliedsvariablen. Konstruktorinitialisierung verwenden. Weisen Sie den Referenzwert der linken Speichervariablen zu. 2. Analyse des Neuordnungsprozesses Nehmen Sie für das obige Beispiel an, dass der erste Thread den synchronisierten Codeblock eingibt und mit der Erstellung von Objekten beginnt. Aufgrund der Neuordnung wird der normale Objekterstellungsprozess möglicherweise unterbrochen Der Referenzwert wird der linken Speichervariablen zugewiesen, und dann kommt es aufgrund der CPU-Planungszeit zu einem Interrupt aufgrund der Chip-Erschöpfung.

    Wenn der nachfolgende Thread erkennt, dass die Variable instance nicht leer ist, wird sie direkt verwendet. Da Singleton-Objekte nicht instanziiert werden, führt ihre direkte Verwendung zu unerwarteten Ergebnissen.
3. Umgang mit der Befehlsumordnung

(1) AtomicReference-Atomklasse

Verwenden Sie die Atomklasse, um einen Satz verwandter Variablen in ein Objekt zu kapseln, und nutzen Sie die Eigenschaften atomarer Operationen, um das Problem der Befehlsumordnung effektiv zu vermeiden.

public class Singleton {
    private volatile static UserModel instance;
    
    public static UserModel getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new UserModel(2, "B");
                }
            }
        }
        return instance;
    }
}

@Data
@AllArgsConstructor
class UserModel {
    private Integer userId;
    private String userName;
}

Die Atomklasse sollte die erste Wahl sein, um das Problem der Befehlsumordnung in einer Multithread-Umgebung zu lösen. Sie ist nicht nur leicht zu verstehen, sondern auch die zwischen Threads verwendete nicht schwere Mutex-Sperre ist relativ effizient.
rrreee

Wenn Anweisungen für eine Gruppe verwandter Variablen neu angeordnet werden, ist die Verwendung der atomaren Operationsklasse eine bessere Lösung.
🎜🎜 (2) flüchtiges Schlüsselwort 🎜rrreee🎜 4. Verständnis der Befehlsumordnung 🎜🎜1. Befehlsumordnung ist nicht auf Java-Programme beschränkt. Tatsächlich gibt es bei verschiedenen Compilern Befehlsumlagerungen, die von der Software bis zur CPU reichen Hardware. Die Neuanordnung von Anweisungen ist eine Leistungsoptimierung für Single-Thread-Programme. Es sollte klar sein, dass die Neuanordnung von Anweisungen die erwarteten Ergebnisse der sequentiellen Programmausführung in einer Single-Thread-Umgebung nicht verändert. 🎜🎜2. Befehlsumordnung in Multithread-Umgebungen🎜🎜Die oben genannten Umstellungen von Befehlen in zwei typischen Multithread-Umgebungen wurden analysiert, ihre negativen Auswirkungen wurden analysiert und entsprechende Gegenmaßnahmen bereitgestellt. 🎜🎜🎜🎜Für zugehörige Variablen kapseln Sie sie zunächst in ein Objekt und verwenden Sie dann atomare Klassen zum Betrieb. 🎜🎜🎜🎜Für neue Objekte verwenden Sie das Schlüsselwort volatile, um das Zielobjekt zu ändern. 🎜🎜🎜🎜3 Synchronisierte Sperren haben nichts zu tun mit der Neuordnung tun🎜 🎜Synchronisierte Sperre stellt sicher, dass Threads durch Mutex-Sperren geordnet auf bestimmte Codeblöcke zugreifen. Der Code innerhalb des Codeblocks wird normalerweise entsprechend der vom Compiler implementierten Strategie neu angeordnet. 🎜🎜Obwohl synchronisierte Sperren die nachteiligen Auswirkungen einer Neuordnung in einer Multithread-Umgebung vermeiden können, ist der durch Mutex-Sperren verursachte Thread-Overhead relativ groß und wird nicht empfohlen. 🎜🎜🎜Nichtatomare Operationen in synchronisierten Blöcken können immer noch zu einer Neuordnung der Befehle führen🎜🎜

Das obige ist der detaillierte Inhalt vonSo lösen Sie die Neuordnung von Java-Anweisungen in einer Multithread-Umgebung. 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