OpenJDK Project Loom

1. Panoramica

In questo articolo, daremo una rapida occhiata a Project Loom. In sostanza, l'obiettivo principale di Project Loom è supportare un modello di concorrenza leggero e ad alta velocità in Java.

2. Project Loom

Project Loom è un tentativo della comunità OpenJDK di introdurre in Java un costrutto di concorrenza leggero. I prototipi per Loom finora hanno introdotto una modifica nella JVM e nella libreria Java.

Sebbene non ci sia ancora un rilascio programmato per Loom, possiamo accedere ai recenti prototipi sul wiki di Project Loom.

Prima di discutere i vari concetti di Loom, esaminiamo l'attuale modello di concorrenza in Java.

3. Modello di concorrenza di Java

Attualmente, Thread rappresenta l'astrazione principale della concorrenza in Java. Questa astrazione, insieme ad altre API simultanee, semplifica la scrittura di applicazioni simultanee.

Tuttavia, poiché Java utilizza i thread del kernel del sistema operativo per l'implementazione, non riesce a soddisfare i requisiti di concorrenza odierni. Ci sono due problemi principali in particolare:

  1. I thread non possono corrispondere alla scala dell'unità di concorrenza del dominio. Ad esempio, le applicazioni di solito consentono fino a milioni di transazioni, utenti o sessioni. Tuttavia, il numero di thread supportati dal kernel è molto inferiore. Pertanto, un T hread per ogni utente, transazione o sessione spesso non è fattibile.
  2. La maggior parte delle applicazioni simultanee richiede una certa sincronizzazione tra i thread per ogni richiesta. A causa di ciò, si verifica un costoso cambio di contesto tra i thread del sistema operativo.

Una possibile soluzione a tali problemi è l'uso di API simultanee asincrone . Esempi comuni sono CompletableFuture e RxJava. A condizione che tali API non blocchino il thread del kernel, fornisce a un'applicazione un costrutto di concorrenza più dettagliato sopra i thread Java .

D'altra parte, tali API sono più difficili da eseguire il debug e da integrare con le API legacy . E quindi, è necessario un costrutto di concorrenza leggero che sia indipendente dai thread del kernel.

4. Attività e utilità di pianificazione

Qualsiasi implementazione di un thread, leggero o pesante, dipende da due costrutti:

  1. Attività (nota anche come continuazione) - Una sequenza di istruzioni che può sospendersi per alcune operazioni di blocco
  2. Scheduler - Per assegnare la continuazione alla CPU e riassegnare la CPU da una continuazione sospesa

Attualmente, Java si basa sulle implementazioni del sistema operativo sia per la continuazione che per lo scheduler .

Ora, per sospendere una continuazione, è necessario memorizzare l'intero stack di chiamate. Allo stesso modo, recupera lo stack di chiamate alla ripresa. Poiché l'implementazione del sistema operativo delle continuazioni include lo stack di chiamate nativo insieme allo stack di chiamate di Java, si traduce in un ingombro pesante .

Un problema più grande, tuttavia, è l'uso dello scheduler del sistema operativo. Poiché lo scheduler viene eseguito in modalità kernel, non c'è differenza tra i thread. E tratta ogni richiesta della CPU nello stesso modo.

Questo tipo di pianificazione non è ottimale per le applicazioni Java in particolare .

Ad esempio, si consideri un thread dell'applicazione che esegue alcune azioni sulle richieste e quindi passa i dati a un altro thread per un'ulteriore elaborazione. In questo caso, sarebbe meglio programmare entrambi questi thread sulla stessa CPU . Ma poiché lo scheduler è agnostico rispetto al thread che richiede la CPU, ciò è impossibile da garantire.

Project Loom propone di risolvere questo problema attraverso thread in modalità utente che si basano sull'implementazione del runtime Java di continuazioni e scheduler invece dell'implementazione del sistema operativo .

5. Fibre

Nei recenti prototipi in OpenJDK, una nuova classe denominata Fiber viene introdotta nella libreria insieme alla classe Thread .

Poiché la libreria pianificata per Fibers è simile a Thread , anche l'implementazione dell'utente dovrebbe rimanere simile. Tuttavia, ci sono due differenze principali:

  1. Fibre sarebbe avvolgere qualsiasi compito in continuazione in modalità utente interno. Ciò consentirebbe all'attività di sospendere e riprendere nel runtime Java invece che nel kernel
  2. Verrà utilizzato uno scheduler in modalità utente collegabile ( ForkJoinPool, ad esempio)

Esaminiamo in dettaglio questi due elementi.

6. Continuazioni

Una continuazione (o co-routine) è una sequenza di istruzioni che può fornire e essere ripresa dal chiamante in una fase successiva.

Ogni continuazione ha un punto di ingresso e un punto di snervamento. Il punto di snervamento è dove è stato sospeso. Ogni volta che il chiamante riprende la continuazione, il controllo torna all'ultimo punto di rendimento.

È importante rendersi conto che questa sospensione / ripresa ora si verifica nel Language Runtime anziché nel sistema operativo . Pertanto, impedisce il costoso cambio di contesto tra i thread del kernel.

Simile ai fili, Project Loom mira a supportare le fibre annidate. Poiché le fibre si basano sulle continuazioni internamente, devono supportare anche le continuazioni annidate. Per comprenderlo meglio, considera una continuazione di classe che consenta l'annidamento:

Continuation cont1 = new Continuation(() -> { Continuation cont2 = new Continuation(() -> { //do something suspend(SCOPE_CONT_2); suspend(SCOPE_CONT_1); }); });

Come mostrato sopra, la continuazione annidata può sospendere se stessa o una qualsiasi delle continuazioni che la racchiudono passando una variabile di ambito . Per questo motivo, sono note come continuazioni con ambito .

Poiché la sospensione di una continuazione richiederebbe anche l'archiviazione dello stack di chiamate, è anche un obiettivo del progetto Loom aggiungere un recupero dello stack leggero mentre si riprende la continuazione.

7. Scheduler

In precedenza, abbiamo discusso le carenze dello scheduler del sistema operativo nella pianificazione di thread relazionabili sulla stessa CPU.

Sebbene l'obiettivo di Project Loom sia quello di consentire scheduler collegabili con fibre, ForkJoinPool in modalità asincrona verrà utilizzato come scheduler predefinito.

ForkJoinPool works on the work-stealing algorithm. Thus, every thread maintains a task deque and executes the task from its head. Furthermore, any idle thread does not block, waiting for the task and pulls it from the tail of another thread's deque instead.

The only difference in asynchronous mode is that the worker threads steal the task from the head of another deque.

ForkJoinPool adds a task scheduled by another running task to the local queue. Hence, executing it on the same CPU.

8. Conclusion

In this article, we discussed the problems in Java's current concurrency model and the changes proposed by Project Loom.

In tal modo, abbiamo anche definito attività e scheduler e osservato come Fibers e ForkJoinPool potrebbero fornire un'alternativa a Java utilizzando i thread del kernel.