JavaRush /Java Blog /Random-IT /Pausa caffè #113. 5 cose che probabilmente non sapevi sul...

Pausa caffè #113. 5 cose che probabilmente non sapevi sul multithreading in Java. 10 estensioni JetBrains per combattere il debito tecnico

Pubblicato nel gruppo Random-IT

5 cose che probabilmente non sapevi sul multithreading in Java

Fonte: DZone Thread è il cuore del linguaggio di programmazione Java. Anche l'esecuzione del programma Hello World necessita del thread principale. Se necessario possiamo aggiungere altri thread al programma se vogliamo che il codice della nostra applicazione sia più funzionale e performante. Se stiamo parlando di un server Web, elabora contemporaneamente centinaia di richieste contemporaneamente. A questo scopo vengono utilizzati più thread. Pausa caffè #113.  5 cose che probabilmente non sapevi sul multithreading in Java.  10 estensioni JetBrains per combattere il debito tecnico - 1I thread sono senza dubbio utili, ma lavorare con essi può essere difficile per molti sviluppatori. In questo articolo condividerò cinque concetti di multithreading che gli sviluppatori nuovi ed esperti potrebbero non conoscere.

1. L'ordine del programma e l'ordine di esecuzione non corrispondono

Quando scriviamo il codice, presupponiamo che verrà eseguito esattamente nel modo in cui lo scriviamo. Tuttavia, in realtà non è così. Il compilatore Java può modificare l'ordine di esecuzione per ottimizzarlo se può determinare che l'output non cambierà nel codice a thread singolo. Guarda il seguente frammento di codice:
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();
    }
}
Questo codice sembra semplice. Abbiamo due istanze di dati condivisi ( sharedData1 e sharedData2 ) che utilizzano due thread. Quando eseguiamo il codice, ci aspettiamo che l'output sia simile a questo:
Discussione 2: 3 Discussione 1: 0,0,0
Ma se esegui il codice più volte, vedrai un risultato diverso:
Filo 2: 3 Filo 1: 3,0,3 Filo 2: 3 Filo 1: 0,0,3 Filo 2: 3 Filo 1: 3,3,3 Filo 2: 3 Filo 1: 0,3,0 Filo 2 : 3 Filo 1: 0,3,3
Non sto dicendo che tutti questi flussi verranno riprodotti esattamente in questo modo sul tuo computer, ma è del tutto possibile.

2. Il numero di thread Java è limitato

Creare un thread in Java è facile. Tuttavia, ciò non significa che possiamo crearne quanti vogliamo. Il numero di thread è limitato. Possiamo facilmente scoprire quanti thread possiamo creare su una particolare macchina utilizzando il seguente programma:
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();
        }
    }
}
Il programma sopra è molto semplice. Crea un thread in un loop e poi lo parcheggia, il che significa che il thread è disabilitato per un uso futuro ma esegue una chiamata di sistema e alloca memoria. Il programma continua a creare thread finché non ne può più creare altri, quindi genera un'eccezione. Siamo interessati al numero che riceveremo finché il programma non genererà un'eccezione. Sul mio computer sono riuscito a creare solo 4065 thread.

3. Troppi thread non garantiscono prestazioni migliori

È ingenuo credere che semplificare i thread in Java migliorerà le prestazioni dell'applicazione. Sfortunatamente, questo presupposto è sbagliato con il nostro tradizionale modello multithreading fornito oggi da Java. In effetti, troppi thread possono ridurre le prestazioni di un'applicazione. Poniamo innanzitutto questa domanda: qual è il numero massimo ottimale di thread che possiamo creare per massimizzare le prestazioni dell'applicazione? Ebbene, la risposta non è così semplice. Dipende molto dal tipo di lavoro che facciamo. Se abbiamo più attività indipendenti, tutte computazionali e che non bloccano alcuna risorsa esterna, avere un numero elevato di thread non migliorerà molto le prestazioni. Se invece disponiamo di un processore a 8 core, il numero ottimale di thread potrebbe essere (8 + 1). In tal caso, possiamo fare affidamento sul thread parallelo introdotto in Java 8. Per impostazione predefinita, il thread parallelo utilizza il pool Fork/Join condiviso. Crea thread pari al numero di processori disponibili, che è sufficiente per consentire loro di lavorare intensamente. L'aggiunta di più thread a un lavoro ad uso intensivo della CPU in cui nulla è bloccato non migliorerà le prestazioni. Piuttosto, sprecheremo semplicemente risorse. Nota. Il motivo per avere un thread aggiuntivo è che anche un thread ad alta intensità di calcolo a volte causa un errore di pagina o viene sospeso per qualche altro motivo. (Vedi: Java Parallelism in Practice , Brian Goetz, pagina 170) Tuttavia, supponiamo, ad esempio, che le attività siano legate all'I/O. In questo caso, dipendono dalla comunicazione esterna (ad esempio database, altre API), quindi ha senso un numero maggiore di thread. Il motivo è che quando un thread è in attesa sull'API Rest, altri thread possono continuare a funzionare. Ora possiamo chiederci ancora: quanti thread sono troppi per un caso del genere? Dipende. Non esistono numeri perfetti che si adattino a tutti i casi. Pertanto, dobbiamo eseguire test adeguati per scoprire cosa funziona meglio per il nostro carico di lavoro e la nostra applicazione specifici. Nello scenario più tipico, di solito abbiamo una serie mista di attività. E in questi casi le cose vanno a compimento. Nel suo libro “Java Concurrency in Practice”, Brian Goetz ha proposto una formula che possiamo utilizzare nella maggior parte dei casi. Numero di thread = Numero di core disponibili * (1 + Tempo di attesa/Tempo di servizio) Il tempo di attesa può essere IO, ad esempio l'attesa di una risposta HTTP, l'acquisizione di un blocco e così via. Tempo di servizio(Tempo di servizio) è il tempo di calcolo, ad esempio l'elaborazione di una risposta HTTP, il marshalling/unmarshalling e così via. Ad esempio, un'applicazione chiama un'API e quindi la elabora. Se disponiamo di 8 processori sul server delle applicazioni, il tempo medio di risposta dell'API è 100 ms e il tempo di elaborazione della risposta è 20 ms, la dimensione del thread ideale sarebbe:
N = 8 * ( 1 + 100/20) = 48
Tuttavia, questa è una semplificazione eccessiva; test adeguati sono sempre fondamentali per determinare il numero.

4. Il multithreading non è parallelismo

A volte usiamo il multithreading e il parallelismo in modo intercambiabile, ma questo non è più del tutto rilevante. Sebbene in Java otteniamo entrambi utilizzando un thread, sono due cose diverse. “Nella programmazione, il multithreading è un caso speciale indipendentemente dai processi in esecuzione, e il parallelismo è l’esecuzione simultanea di calcoli (eventualmente correlati). Il multithreading riguarda l'interazione con molte cose contemporaneamente. La concorrenza significa fare molte cose contemporaneamente”. La definizione di cui sopra data da Rob Pike è abbastanza accurata. Diciamo che abbiamo compiti completamente indipendenti e possono essere calcolati separatamente. In questo caso, queste attività sono chiamate parallele e possono essere eseguite con un pool Fork/Join o un thread parallelo. D'altra parte, se abbiamo molti compiti, alcuni di essi potrebbero dipendere da altri. Il modo in cui componiamo e strutturiamo si chiama multithreading. Ha a che fare con la struttura. Potremmo voler eseguire più attività contemporaneamente per ottenere un determinato risultato, senza necessariamente finirne una più velocemente.

5. Project Loom ci consente di creare milioni di thread

Nel punto precedente ho sostenuto che avere più thread non significa migliorare le prestazioni dell’applicazione. Tuttavia, nell’era dei microservizi, interagiamo con troppi servizi per svolgere un lavoro specifico. In uno scenario di questo tipo, i thread rimangono in uno stato bloccato per la maggior parte del tempo. Sebbene un sistema operativo moderno possa gestire milioni di socket aperti, non possiamo aprire molti canali di comunicazione poiché siamo limitati dal numero di thread. Ma cosa succede se crei milioni di thread e ognuno di essi utilizza un socket aperto per comunicare con il mondo esterno? Ciò migliorerà sicuramente la velocità effettiva della nostra applicazione. Per supportare questa idea, esiste un'iniziativa in Java chiamata Project Loom. Usandolo, possiamo creare milioni di thread virtuali. Ad esempio, utilizzando il seguente frammento di codice, sono riuscito a creare 4,5 milioni di thread sul mio computer.
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();
            });
        }
    }
}
Per eseguire questo programma è necessario avere installato Java 18, che può essere scaricato qui . È possibile eseguire il codice utilizzando il comando seguente: java --source 18 --enable-preview Main.java

10 estensioni JetBrains per combattere il debito tecnico

Fonte: DZone Molti team di sviluppo avvertono un'enorme pressione nel rispettare le scadenze. Per questo motivo, spesso non hanno abbastanza tempo per sistemare e ripulire la propria base di codice. A volte in queste situazioni il debito tecnico si accumula rapidamente. Le estensioni dell'editor possono aiutare a risolvere questo problema. Diamo un'occhiata alle 10 migliori estensioni JetBrains per combattere il debito tecnico (con supporto Java). Pausa caffè #113.  5 cose che probabilmente non sapevi sul multithreading in Java.  10 estensioni JetBrains per combattere il debito tecnico - 2

Strumenti di refactoring e debito tecnico

1. RefactorInsight

RefactorInsight migliora la visibilità delle modifiche al codice nell'IDE fornendo informazioni sui refactoring.
  1. L'estensione definisce i refactoring nelle richieste di unione.
  2. Contrassegna i commit che contengono refactoring.
  3. Aiuta a visualizzare il refactoring di qualsiasi commit specifico selezionato nella scheda Git Log.
  4. Mostra la cronologia del refactoring di classi, metodi e campi.

2. Tracker dei problemi Stepsize nell'IDE

Stepsize è un ottimo tracker dei problemi per gli sviluppatori. L'estensione aiuta gli ingegneri non solo a creare TODO e commenti sul codice migliori, ma anche a dare priorità al debito tecnico, ai refactoring e simili:
  1. Stepsize ti consente di creare e visualizzare attività nel codice direttamente nell'editor.
  2. Trova i problemi che influiscono sulle funzionalità su cui stai lavorando.
  3. Aggiungi problemi ai tuoi sprint utilizzando le integrazioni Jira, Asana, Linear, Azure DevOps e GitHub.

3. Nuovo CodeStream della reliquia

New Relic CodeStream è una piattaforma di collaborazione tra sviluppatori per discutere e rivedere il codice. Supporta richieste pull da GitHub, BitBucket e GitLab, gestione dei problemi da Jira, Trello, Asana e altri 9 e fornisce discussioni sul codice, collegando il tutto.
  1. Crea, rivedi e unisci richieste pull in GitHub.
  2. Ottieni feedback sul lavoro in corso con revisioni preliminari del codice.
  3. Discutere i problemi di codice con i compagni di squadra.

TODO e commenti

4. Evidenziatore di commenti

Questo plugin ti consente di creare un'evidenziazione personalizzata delle righe di commento e delle parole chiave della lingua. Il plugin ha anche la capacità di definire token personalizzati per evidenziare le righe di commento.

5. Commenti migliori

L'estensione Better Comments ti aiuta a creare commenti più chiari nel tuo codice. Con questa estensione potrai classificare le tue annotazioni in:
  1. Avvisi.
  2. Richieste.
  3. FARE.
  4. Momenti fondamentali.

Bug e vulnerabilità della sicurezza

6.SonarLint_ _

SonarLint ti consente di risolvere i problemi del codice prima che si presentino. Può anche essere usato come correttore ortografico. SonarLint evidenzia bug e vulnerabilità della sicurezza durante la scrittura del codice, con chiare istruzioni di risoluzione in modo da poterle correggere prima che il codice venga eseguito.

7. SpotBugs

Il plugin SpotBugs fornisce l'analisi statica del bytecode per trovare bug nel codice Java di IntelliJ IDEA. SpotBugs è uno strumento di rilevamento dei difetti per Java che utilizza l'analisi statica per trovare oltre 400 modelli di bug come dereferenziazioni di puntatori nulli, cicli ricorsivi infiniti, uso improprio delle librerie Java e deadlock. SpotBugs può identificare centinaia di difetti gravi in ​​applicazioni di grandi dimensioni (tipicamente circa 1 difetto ogni 1000-2000 righe di dichiarazioni grezze non commentate).

8. Scanner delle vulnerabilità Snyk

Il Vulnerability Scanner di Snyk ti aiuta a trovare e correggere le vulnerabilità della sicurezza e i problemi di qualità del codice nei tuoi progetti.
  1. Individuazione e risoluzione dei problemi di sicurezza.
  2. Visualizza un elenco di diversi tipi di problemi, suddivisi in categorie.
  3. Visualizza suggerimenti per la risoluzione dei problemi.

9. Localizzatore di caratteri a larghezza zero

Questo plugin migliora il controllo e il rilevamento di errori difficili da trovare relativi a caratteri invisibili di larghezza zero nel codice sorgente e nelle risorse. Durante l'utilizzo, assicurarsi che il controllo "Carattere Unicode a larghezza zero" sia abilitato.

10.CodiceMR _

CodeMR è uno strumento di analisi statica e di qualità del software che aiuta le società di software a sviluppare codici e programmi migliori. CodeMR visualizza le metriche del codice e gli attributi di qualità di alto livello (accoppiamento, complessità, coesione e dimensione) in varie visualizzazioni come Struttura del pacchetto, TreeMap, Sunburst, Dipendenza e Visualizzazioni grafico.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION