Utilizzando il MapMaker di Guava

1. Introduzione

MapMaker è una classe builder in Guava che semplifica la creazione di mappe thread-safe.

Java supporta già WeakHashMap per utilizzare i riferimenti deboli per le chiavi. Ma non esiste una soluzione pronta all'uso da utilizzare per i valori. Fortunatamente, MapMaker fornisce semplici metodi di creazione per utilizzare WeakReference sia per le chiavi che per i valori .

In questo tutorial, vediamo come MapMaker semplifica la creazione di più mappe e l'utilizzo di riferimenti deboli.

2. Dipendenza da Maven

Prima di tutto, aggiungiamo la dipendenza Google Guava, disponibile su Maven Central:

 com.google.guava guava 29.0-jre 

3. Un esempio di memorizzazione nella cache

Consideriamo un semplice scenario di un server che mantiene un paio di cache per gli utenti: una cache di sessione e una cache di profilo.

La cache della sessione è di breve durata e le sue voci diventano non valide dopo che l'utente non è più attivo. Quindi la cache può rimuovere la voce per l'utente dopo che l'oggetto utente è stato sottoposto a garbage collection.

La cache del profilo, tuttavia, può avere un time-to-live (TTL) più elevato. Le voci nella cache del profilo diventano non valide solo quando l'utente aggiorna il proprio profilo.

In questo caso, la cache può rimuovere la voce solo quando l'oggetto profilo è sottoposto a garbage collection.

3.1. Strutture dati

Creiamo classi per rappresentare queste entità.

Inizieremo prima con l'utente:

public class User { private long id; private String name; public User(long id, String name) { this.id = id; this.name = name; } public long getId() { return id; } public String getName() { return name; } }

Quindi la sessione:

public class Session { private long id; public Session(long id) { this.id = id; } public long getId() { return id; } } 

E infine il profilo:

public class Profile { private long id; private String type; public Profile(long id, String type) { this.id = id; this.type = type; } public long getId() { return id; } public String getName() { return type; } }

3.2. Creazione delle cache

Creiamo un'istanza di ConcurrentMap per la cache della sessione utilizzando il metodo makeMap :

ConcurrentMap sessionCache = new MapMaker().makeMap();

La mappa restituita non consente valori null sia per la chiave che per il valore.

Ora, creiamo un'altra istanza di ConcurrentMap per la cache del profilo:

ConcurrentMap profileCache = new MapMaker().makeMap();

Si noti che non abbiamo specificato la capacità iniziale per le cache. Quindi, MapMaker crea una mappa di capacità 16 per impostazione predefinita.

Se vogliamo possiamo modificare la capacità utilizzando il metodo initialCapacity :

ConcurrentMap profileCache = new MapMaker().initialCapacity(100).makeMap();

3.3. Modifica del livello di concorrenza

MapMaker imposta il valore predefinito per il livello di concorrenza su 4 . Tuttavia, il sessionCache deve supportare un numero maggiore di aggiornamenti simultanei senza alcun conflitto di thread.

Qui, il metodo del builder concurrencyLevel viene in soccorso:

ConcurrentMap sessionCache = new MapMaker().concurrencyLevel(10).makeMap();

3.4. Utilizzo di riferimenti deboli

Le mappe che abbiamo creato sopra utilizzano riferimenti forti sia per le chiavi che per i valori. Quindi, le voci rimangono nella mappa anche se le chiavi ei valori vengono raccolti in modo indesiderato. Dovremmo invece usare riferimenti deboli.

Una voce sessionCache non è valida dopo che la chiave (l'oggetto utente) è stata sottoposta a garbage collection . Quindi, usiamo riferimenti deboli per le chiavi:

ConcurrentMap sessionCache = new MapMaker().weakKeys().makeMap();

Per il profileCache , possiamo usare riferimenti deboli per i valori:

ConcurrentMap profileCache = new MapMaker().weakValues().makeMap();

Quando questi riferimenti vengono raccolti in modo garbage, Guava garantisce che queste voci non verranno incluse in nessuna delle successive operazioni di lettura o scrittura sulla mappa . Tuttavia, il metodo size () potrebbe talvolta essere incoerente e può includere queste voci .

4. MapMaker Internals

MapMaker crea una ConcurrentHashMap per impostazione predefinita se i riferimenti deboli non sono abilitati . I controlli di uguaglianza avvengono tramite il solito metodo di uguaglianza.

Se abilitiamo i riferimenti deboli, MapMaker crea internamente una mappa personalizzata rappresentata da un insieme di tabelle hash. Condivide anche caratteristiche di prestazioni simili come ConcurrentHashMap .

Tuttavia, una differenza importante con WeakHashMap è che i controlli di uguaglianza avvengono tramite i confronti di identità (== e identityHashCode ).

5. conclusione

In questo breve articolo abbiamo appreso come utilizzare la classe MapMaker per creare una mappa thread-safe. Abbiamo anche visto come personalizzare la mappa per utilizzare riferimenti deboli.

Come sempre, il codice sorgente completo dell'articolo è disponibile su GitHub.