JavaRush /Java-Blog /Random-DE /Kaffeepause Nr. 113. 5 Dinge, die Sie wahrscheinlich nich...

Kaffeepause Nr. 113. 5 Dinge, die Sie wahrscheinlich nicht über Multithreading in Java wussten. 10 JetBrains-Erweiterungen zur Bekämpfung technischer Schulden

Veröffentlicht in der Gruppe Random-DE

5 Dinge, die Sie wahrscheinlich nicht über Multithreading in Java wussten

Quelle: DZone Thread ist das Herzstück der Programmiersprache Java. Sogar das Ausführen des Hello World-Programms erfordert den Hauptthread. Bei Bedarf können wir dem Programm weitere Threads hinzufügen, wenn unser Anwendungscode funktionaler und leistungsfähiger sein soll. Wenn es sich um einen Webserver handelt, verarbeitet dieser gleichzeitig Hunderte von Anfragen. Hierzu werden mehrere Threads verwendet. Kaffeepause Nr. 113.  5 Dinge, die Sie wahrscheinlich nicht über Multithreading in Java wussten.  10 JetBrains-Erweiterungen zur Bekämpfung technischer Schulden – 1Threads sind zweifellos nützlich, aber die Arbeit mit ihnen kann für viele Entwickler schwierig sein. In diesem Artikel werde ich fünf Multithreading-Konzepte vorstellen, die neue und erfahrene Entwickler möglicherweise nicht kennen.

1. Programmreihenfolge und Ausführungsreihenfolge stimmen nicht überein

Wenn wir Code schreiben, gehen wir davon aus, dass er genau so ausgeführt wird, wie wir ihn geschrieben haben. In Wirklichkeit ist dies jedoch nicht der Fall. Der Java-Compiler kann die Ausführungsreihenfolge ändern, um sie zu optimieren, wenn er feststellen kann, dass sich die Ausgabe im Single-Thread-Code nicht ändert. Schauen Sie sich den folgenden Codeausschnitt an:
package ca.bazlur.playground;

import java.util.concurrent.Phaser;

public class ExecutionOrderDemo {
    private static class A {
        int x = 0;
    }

    private static final A sharedData1 = new A();
    private static final A sharedData2 = new A();

    public static void main(String[] args) {
        var phaser = new Phaser(3);
        var t1 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l1 = sharedData1;
            var l2 = l1.x;
            var l3 = sharedData2;
            var l4 = l3.x;
            var l5 = l1.x;
            System.out.println("Thread 1: " + l2 + "," + l4 + "," + l5);
        });
        var t2 = new Thread(() -> {
            phaser.arriveAndAwaitAdvance();
            var l6 = sharedData1;
            l6.x = 3;
            System.out.println("Thread 2: " + l6.x);
        });
        t1.start();
        t2.start();
        phaser.arriveAndDeregister();
    }
}
Dieser Code scheint einfach zu sein. Wir haben zwei gemeinsam genutzte Dateninstanzen ( sharedData1 und sharedData2 ), die zwei Threads verwenden. Wenn wir den Code ausführen, erwarten wir, dass die Ausgabe so aussieht:
Thread 2: 3 Thread 1: 0,0,0
Wenn Sie den Code jedoch mehrmals ausführen, werden Sie ein anderes Ergebnis sehen:
Thread 2: 3 Thread 1: 3,0,3 Thread 2: 3 Thread 1: 0,0,3 Thread 2: 3 Thread 1: 3,3,3 Thread 2: 3 Thread 1: 0,3,0 Thread 2 : 3 Thread 1: 0,3,3
Ich behaupte nicht, dass alle diese Streams auf Ihrem Computer genau so abgespielt werden, aber es ist durchaus möglich.

2. Die Anzahl der Java-Threads ist begrenzt

Das Erstellen eines Threads in Java ist einfach. Das bedeutet jedoch nicht, dass wir so viele davon erstellen können, wie wir möchten. Die Anzahl der Threads ist begrenzt. Mit dem folgenden Programm können wir ganz einfach herausfinden, wie viele Threads wir auf einer bestimmten Maschine erstellen können:
package ca.bazlur.playground;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Playground {
    public static void main(String[] args) {
        var counter = new AtomicInteger();
        while (true) {
            new Thread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            }).start();
        }
    }
}
Das obige Programm ist sehr einfach. Es erstellt einen Thread in einer Schleife und parkt ihn dann, was bedeutet, dass der Thread für die zukünftige Verwendung deaktiviert ist, aber einen Systemaufruf ausführt und Speicher zuweist. Das Programm erstellt so lange Threads, bis keine weiteren Threads mehr erstellt werden können, und löst dann eine Ausnahme aus. Uns interessiert die Zahl, die wir erhalten, bis das Programm eine Ausnahme auslöst. Auf meinem Computer konnte ich nur 4065 Threads erstellen.

3. Zu viele Threads garantieren keine bessere Leistung

Es ist naiv zu glauben, dass die Vereinfachung von Threads in Java die Anwendungsleistung verbessern wird. Leider ist diese Annahme bei unserem traditionellen Multithreading-Modell, das Java heute bietet, falsch. Tatsächlich können zu viele Threads die Leistung einer Anwendung beeinträchtigen. Stellen wir uns zunächst die Frage: Was ist die optimale maximale Anzahl an Threads, die wir erstellen können, um die Anwendungsleistung zu maximieren? Nun, die Antwort ist nicht so einfach. Es hängt sehr stark von der Art der Arbeit ab, die wir machen. Wenn wir mehrere unabhängige Aufgaben haben, die alle rechnerisch sind und keine externen Ressourcen blockieren, wird eine große Anzahl von Threads die Leistung nicht wesentlich verbessern. Wenn wir hingegen einen 8-Kern-Prozessor haben, könnte die optimale Anzahl an Threads (8 + 1) sein. In einem solchen Fall können wir uns auf den in Java 8 eingeführten Parallel-Thread verlassen. Standardmäßig verwendet der Parallel-Thread den gemeinsam genutzten Fork/Join-Pool. Es werden Threads erstellt, die der Anzahl der verfügbaren Prozessoren entsprechen, was ausreicht, um intensiv arbeiten zu können. Das Hinzufügen weiterer Threads zu einem CPU-intensiven Job, bei dem nichts blockiert ist, führt nicht zu einer Leistungsverbesserung. Vielmehr werden wir einfach Ressourcen verschwenden. Notiz. Der Grund für einen zusätzlichen Thread liegt darin, dass selbst ein rechenintensiver Thread manchmal einen Seitenfehler verursacht oder aus einem anderen Grund angehalten wird. (Siehe: Java Parallelism in Practice , Brian Goetz, Seite 170) Nehmen wir jedoch beispielsweise an, dass die Aufgaben E/A-gebunden sind. In diesem Fall sind sie auf externe Kommunikation (z. B. Datenbank, andere APIs) angewiesen, sodass eine größere Anzahl an Threads sinnvoll ist. Der Grund dafür ist, dass andere Threads weiterarbeiten können, wenn ein Thread auf die Rest-API wartet. Jetzt können wir noch einmal fragen: Wie viele Threads sind zu viele für einen solchen Fall? Kommt darauf an. Es gibt keine perfekten Zahlen, die für alle Fälle passen. Daher müssen wir angemessene Tests durchführen, um herauszufinden, was für unsere spezifische Arbeitslast und Anwendung am besten funktioniert. Im typischsten Szenario haben wir normalerweise eine Reihe gemischter Aufgaben. Und in solchen Fällen kommt es zur Vollendung. Brian Goetz hat in seinem Buch „Java Concurrency in Practice“ eine Formel vorgeschlagen, die wir in den meisten Fällen verwenden können. Anzahl der Threads = Anzahl der verfügbaren Kerne * (1 + Wartezeit / Servicezeit) Die Wartezeit kann E/A sein, z. B. das Warten auf eine HTTP-Antwort, das Erlangen einer Sperre usw. Servicezeit(Dienstzeit) ist die Rechenzeit, z. B. für die Verarbeitung einer HTTP-Antwort, das Marshalling/Unmarshaling usw. Beispielsweise ruft eine Anwendung eine API auf und verarbeitet sie dann. Wenn wir 8 Prozessoren auf dem Anwendungsserver haben, die durchschnittliche API-Antwortzeit 100 ms und die Antwortverarbeitungszeit 20 ms beträgt, dann wäre die ideale Thread-Größe:
N = 8 * ( 1 + 100/20) = 48
Dies ist jedoch eine zu starke Vereinfachung; Für die Bestimmung der Anzahl sind immer angemessene Tests von entscheidender Bedeutung.

4. Multithreading ist keine Parallelität

Manchmal verwenden wir Multithreading und Parallelität synonym, aber das ist nicht mehr ganz relevant. Obwohl wir in Java beides mithilfe eines Threads erreichen, handelt es sich dabei um zwei verschiedene Dinge. „In der Programmierung ist Multithreading unabhängig von den laufenden Prozessen ein Sonderfall und Parallelität ist die gleichzeitige Ausführung von (möglicherweise zusammenhängenden) Berechnungen.“ Beim Multithreading geht es darum , mit vielen Dingen gleichzeitig zu interagieren . Parallelität bedeutet, viele Dinge gleichzeitig zu erledigen .“ Die obige Definition von Rob Pike ist ziemlich zutreffend. Nehmen wir an, wir haben völlig unabhängige Aufgaben, die separat berechnet werden können. In diesem Fall werden diese Aufgaben als parallel bezeichnet und können mit einem Fork/Join-Pool oder einem parallelen Thread ausgeführt werden. Wenn wir andererseits viele Aufgaben haben, können einige von ihnen von anderen abhängen. Die Art und Weise, wie wir komponieren und strukturieren, wird Multithreading genannt. Es hat mit Struktur zu tun. Möglicherweise möchten wir mehrere Aufgaben gleichzeitig ausführen, um ein bestimmtes Ergebnis zu erzielen, ohne eine Aufgabe unbedingt schneller zu erledigen.

5. Mit Project Loom können wir Millionen von Threads erstellen

Im vorherigen Punkt habe ich argumentiert, dass mehr Threads keine verbesserte Anwendungsleistung bedeuten. Im Zeitalter der Microservices interagieren wir jedoch mit zu vielen Diensten, um bestimmte Aufgaben zu erledigen. In einem solchen Szenario bleiben Threads die meiste Zeit in einem blockierten Zustand. Während ein modernes Betriebssystem Millionen offener Sockets verarbeiten kann, können wir nicht viele Kommunikationskanäle öffnen, da wir durch die Anzahl der Threads begrenzt sind. Was aber, wenn Sie Millionen von Threads erstellen und jeder von ihnen einen offenen Socket verwendet, um mit der Außenwelt zu kommunizieren? Dies wird unseren Anwendungsdurchsatz definitiv verbessern. Um diese Idee zu unterstützen, gibt es in Java eine Initiative namens Project Loom. Damit können wir Millionen virtueller Threads erstellen. Mit dem folgenden Codeausschnitt konnte ich beispielsweise 4,5 Millionen Threads auf meinem Computer erstellen.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;

public class Main {
    public static void main(String[] args) {
        var counter = new AtomicInteger();

        // 4_576_279
        while (true) {
            Thread.startVirtualThread(() -> {
                int count = counter.incrementAndGet();
                System.out.println("thread count = " + count);
                LockSupport.park();
            });
        }
    }
}
Um dieses Programm auszuführen, muss Java 18 installiert sein, das hier heruntergeladen werden kann . Sie können den Code mit dem folgenden Befehl ausführen: java --source 18 --enable-preview Main.java

10 JetBrains-Erweiterungen zur Bekämpfung technischer Schulden

Quelle: DZone Viele Entwicklungsteams verspüren einen enormen Druck, Fristen einzuhalten. Aus diesem Grund haben sie oft nicht genug Zeit, ihre Codebasis zu reparieren und zu bereinigen. Manchmal häufen sich in solchen Situationen schnell technische Schulden an. Editor-Erweiterungen können helfen, dieses Problem zu lösen. Werfen wir einen Blick auf die 10 besten JetBrains-Erweiterungen zur Bekämpfung technischer Schulden (mit Java-Unterstützung). Kaffeepause Nr. 113.  5 Dinge, die Sie wahrscheinlich nicht über Multithreading in Java wussten.  10 JetBrains-Erweiterungen zur Bekämpfung technischer Schulden – 2

Refactoring und technische Schuldentools

1. RefactorInsight

RefactorInsight verbessert die Sichtbarkeit von Codeänderungen in der IDE, indem es Informationen über Refactorings bereitstellt.
  1. Die Erweiterung definiert Refactorings in Merge-Anfragen.
  2. Markiert Commits, die Refactorings enthalten.
  3. Hilft beim Anzeigen des Refactorings eines bestimmten Commits, das auf der Registerkarte „Git-Protokoll“ ausgewählt wurde.
  4. Zeigt den Refactoring-Verlauf von Klassen, Methoden und Feldern an.

2. Stepsize Issue Tracker in der IDE

Stepsize ist ein großartiger Issue-Tracker für Entwickler. Die Erweiterung hilft Ingenieuren nicht nur dabei, bessere TODOs und Codekommentare zu erstellen, sondern auch technische Schulden, Refactorings und dergleichen zu priorisieren:
  1. Mit Stepsize können Sie Aufgaben im Code direkt im Editor erstellen und anzeigen.
  2. Finden Sie Probleme, die sich auf die Funktionen auswirken, an denen Sie arbeiten.
  3. Fügen Sie mithilfe von Jira-, Asana-, Linear-, Azure DevOps- und GitHub-Integrationen Probleme zu Ihren Sprints hinzu.

3. Neuer Relikt-CodeStream

New Relic CodeStream ist eine Entwickler-Kollaborationsplattform zur Diskussion und Überprüfung von Code. Es unterstützt Pull-Requests von GitHub, BitBucket und GitLab, Issue-Management von Jira, Trello, Asana und neun anderen und bietet Code-Diskussionen, die alles miteinander verbinden.
  1. Erstellen, überprüfen und führen Sie Pull-Anfragen in GitHub zusammen.
  2. Erhalten Sie Feedback zu laufenden Arbeiten mit vorläufigen Codeüberprüfungen.
  3. Besprechen Sie Codeprobleme mit Teamkollegen.

TODO und Kommentare

4. Textmarker für Kommentare

Mit diesem Plugin können Sie benutzerdefinierte Hervorhebungen von Kommentarzeilen und Sprachschlüsselwörtern erstellen. Das Plugin bietet außerdem die Möglichkeit, benutzerdefinierte Token zum Hervorheben von Kommentarzeilen zu definieren.

5. Bessere Kommentare

Mit der Erweiterung „Better Comments“ können Sie klarere Kommentare in Ihrem Code erstellen. Mit dieser Erweiterung können Sie Ihre Anmerkungen klassifizieren in:
  1. Warnungen.
  2. Anfragen.
  3. MACHEN.
  4. Grundlegende Momente.

Fehler und Sicherheitslücken

6.SonarLint_ _

Mit SonarLint können Sie Codeprobleme beheben, bevor sie auftreten. Es kann auch als Rechtschreibprüfung verwendet werden. SonarLint hebt beim Programmieren Fehler und Sicherheitslücken hervor und enthält klare Anweisungen zur Behebung, sodass Sie diese beheben können, bevor der Code übernommen wird.

7. SpotBugs

Das SpotBugs-Plugin bietet eine statische Bytecode-Analyse, um Fehler im Java-Code von IntelliJ IDEA zu finden. SpotBugs ist ein Fehlererkennungstool für Java, das mithilfe statischer Analyse über 400 Fehlermuster wie Nullzeiger-Dereferenzierungen, unendliche rekursive Schleifen, Missbrauch von Java-Bibliotheken und Deadlocks findet. SpotBugs kann Hunderte schwerwiegender Fehler in großen Anwendungen identifizieren (normalerweise etwa 1 Fehler pro 1000–2000 Zeilen unkommentierter Rohanweisungen).

8. Snyk-Schwachstellenscanner

Der Vulnerability Scanner von Snyk hilft Ihnen, Sicherheitslücken und Codequalitätsprobleme in Ihren Projekten zu finden und zu beheben.
  1. Sicherheitsprobleme finden und beheben.
  2. Sehen Sie sich eine Liste verschiedener Arten von Problemen an, unterteilt in Kategorien.
  3. Zeigt Tipps zur Fehlerbehebung an.

9. Zeichensuche mit Nullbreite

Dieses Plugin verbessert die Überprüfung und Erkennung schwer zu findender Fehler im Zusammenhang mit unsichtbaren Zeichen der Breite Null im Quellcode und in Ressourcen. Stellen Sie bei der Verwendung sicher, dass die Prüfung „Unicode-Zeichen mit Nullbreite“ aktiviert ist.

10.CodeMR _

CodeMR ist ein Tool zur Analyse von Softwarequalität und statischem Code, das Softwareunternehmen dabei hilft, besseren Code und bessere Programme zu entwickeln. CodeMR visualisiert Codemetriken und hochwertige Qualitätsattribute (Kopplung, Komplexität, Kohäsion und Größe) in verschiedenen Ansichten wie Paketstruktur, TreeMap, Sunburst, Abhängigkeit und Diagrammansichten.
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION