Sincronizzazione su un valore intero

Possibile duplicato:
Qual è il modo migliore per aumentare il numero di blocchi in java

Supponiamo che voglio bloccare in base a un valore intero di id. In questo caso, esiste una funzione che estrae un valore da una cache e esegue un recupero / archiviazione abbastanza costoso nella cache se il valore non è presente.

Il codice esistente non è sincronizzato e potrebbe potenzialmente triggersre più operazioni di recupero / archiviazione:

//psuedocode public Page getPage (Integer id){ Page p = cache.get(id); if (p==null) { p=getFromDataBase(id); cache.store(p); } } 

Quello che mi piacerebbe fare è sincronizzare il recupero sull’ID, ad es

  if (p==null) { synchronized (id) { ..retrieve, store } } 

Sfortunatamente questo non funzionerà perché 2 chiamate separate possono avere lo stesso valore Integer ID ma un object intero diverso, quindi non condivideranno il blocco e non si verificherà alcuna sincronizzazione.

C’è un modo semplice per assicurarti di avere la stessa istanza di Integer? Ad esempio, funzionerà questo:

  syncrhonized (Integer.valueOf(id.intValue())){ 

Javadoc per Integer.valueOf () sembra implicare che è probabile che tu riceva la stessa istanza, ma non sembra una garanzia:

Restituisce un’istanza Integer che rappresenta il valore int specificato. Se non è richiesta una nuova istanza Integer, questo metodo deve essere generalmente utilizzato in preferenza rispetto al costruttore Integer (int), poiché è probabile che questo metodo produca prestazioni significativamente migliori in termini di spazio e tempo memorizzando nella cache i valori richiesti di frequente.

Quindi, qualche suggerimento su come ottenere un’istanza di Integer che è garantita per essere la stessa, a parte le soluzioni più elaborate come mantenere una WeakHashMap di Lock oggetti con chiave per l’int? (niente di sbagliato in questo, sembra proprio che ci debba essere un one-liner ovvio di quanto mi manchi).

Davvero non vuoi sincronizzarti su un Integer , dal momento che non hai il controllo su quali istanze sono uguali e quali sono le istanze differenti. Java non fornisce questa funzionalità (a meno che non si utilizzino gli Integer in un intervallo ridotto) che sia affidabile tra diverse JVM. Se davvero devi sincronizzare su un intero, devi mantenere una mappa o un insieme di numeri interi in modo da poter garantire che stai ottenendo l’istanza esatta che desideri.

Meglio sarebbe creare un nuovo object, magari memorizzato in una HashMap che è digitato dal HashMap Integer , per sincronizzarsi. Qualcosa come questo:

 public Page getPage(Integer id) { Page p = cache.get(id); if (p == null) { synchronized (getCacheSyncObject(id)) { p = getFromDataBase(id); cache.store(p); } } } private ConcurrentMap locks = new ConcurrentHashMap(); private Object getCacheSyncObject(final Integer id) { locks.putIfAbsent(id, id); return locks.get(id); } 

Per spiegare questo codice, utilizza ConcurrentMap , che consente l’uso di putIfAbsent . Potresti fare questo:

  locks.putIfAbsent(id, new Object()); 

ma poi si incorre nel (piccolo) costo della creazione di un object per ogni accesso. Per evitare ciò, salgo semplicemente l’intero stesso nella Map . Cosa ottiene? Perché questo è diverso dall’utilizzo del numero intero stesso?

Quando si esegue un get() da una Map , i tasti vengono confrontati con equals() (o almeno il metodo utilizzato equivale all’utilizzo di equals() ). Due diverse istanze Integer dello stesso valore saranno uguali tra loro. Pertanto, è ansible passare un numero qualsiasi di istanze Integer diverse di ” new Integer(5) ” come parametro per getCacheSyncObject e si otterrà sempre solo la prima istanza in cui è stato getCacheSyncObject che conteneva quel valore.

Ci sono dei motivi per cui potresti non voler sincronizzare su Integer … puoi entrare in deadlock se più thread si sincronizzano sugli oggetti Integer e quindi inconsapevolmente usano gli stessi lock quando vogliono usare lock diversi. È ansible correggere questo rischio utilizzando il

  locks.putIfAbsent(id, new Object()); 

versione e quindi incorrere in un (molto) piccolo costo per ogni accesso alla cache. In questo modo, garantisci che questa class eseguirà la sincronizzazione su un object su cui non verrà sincronizzata nessun’altra class. Sempre una buona cosa

Utilizzare una mappa thread-safe, come ConcurrentHashMap . Questo ti permetterà di manipolare una mappa in sicurezza, ma usa un blocco diverso per eseguire il calcolo reale. In questo modo è ansible eseguire più calcoli simultanei con una singola mappa.

Usa ConcurrentMap.putIfAbsent , ma invece di posizionare il valore attuale, usa invece un Future con costruzione computazionalmente leggera. Forse l’implementazione di FutureTask . Esegui il calcolo e get il risultato, che verrà bloccato in modo sicuro fino al completamento.

Integer.valueOf() restituisce solo le istanze memorizzate nella cache per un intervallo limitato. Non hai specificato il tuo intervallo, ma in generale, questo non funzionerà.

Tuttavia, ti consiglio vivamente di non seguire questo approccio, anche se i tuoi valori sono nell’intervallo corretto. Poiché queste istanze Integer memorizzate nella cache sono disponibili per qualsiasi codice, non è ansible controllare completamente la sincronizzazione, il che potrebbe portare a un deadlock. Questo è lo stesso problema che le persone hanno cercato di bloccare sul risultato di String.intern() .

Il miglior blocco è una variabile privata. Poiché solo il tuo codice può fare riferimento a questo, puoi garantire che non si verificheranno deadlock.

A proposito, l’utilizzo di una WeakHashMap non funzionerà neanche. Se l’istanza che funge da chiave non viene referenziata, verrà raccolta automaticamente. E se è fortemente referenziato, potresti usarlo direttamente.

L’uso sincronizzato su un numero intero sembra davvero sbagliato in base alla progettazione.

Se è necessario sincronizzare ciascun elemento singolarmente solo durante il recupero / lo store, è ansible creare un Set e memorizzare lì gli elementi attualmente bloccati. In altre parole,

 // this contains only those IDs that are currently locked, that is, this // will contain only very few IDs most of the time Set activeIds = ... Object retrieve(Integer id) { // acquire "lock" on item #id synchronized(activeIds) { while(activeIds.contains(id)) { try { activeIds.wait(); } catch(InterruptedExcption e){...} } activeIds.add(id); } try { // do the retrieve here... return value; } finally { // release lock on item #id synchronized(activeIds) { activeIds.remove(id); activeIds.notifyAll(); } } } 

Lo stesso vale per il negozio.

La linea di fondo è: non esiste una singola riga di codice che risolva questo problema esattamente come è necessario.

Che ne dici di una ConcurrentHashMap con gli oggetti Integer come chiavi?

Potresti dare un’occhiata a questo codice per creare un mutex da un ID. Il codice è stato scritto per gli ID di stringa, ma potrebbe essere facilmente modificato per gli oggetti Integer.

Vedi la sezione 5.6 in Java Concurrency in Practice : “Costruire una cache dei risultati efficiente, scalabile”. Si occupa del problema esatto che stai cercando di risolvere. In particolare, controlla il pattern del memoizer.

alt text http://www.cs.umd.edu/class/fall2008/cmsc433/jcipMed.jpg

Come puoi vedere dalla varietà di risposte, ci sono vari modi per skinare questo gatto:

  • L’approccio di Goetz et al di tenere una cache di FutureTasks funziona abbastanza bene in situazioni come questa in cui “stai caching qualcosa comunque” quindi non preoccuparti di build una mappa di oggetti FutureTask (e se hai fatto attenzione alla crescita della mappa, almeno è facile renderla concomitante)
  • Come risposta generale a “come bloccare l’ID”, l’approccio delineato da Antonio ha il vantaggio che è ovvio quando viene aggiunta o rimossa la mappa dei blocchi.

Potrebbe essere necessario prestare attenzione a un potenziale problema con l’ implementazione di Antonio, vale a dire che notifyAll () ritriggers i thread in attesa su tutti gli ID quando uno di essi diventa disponibile, il che potrebbe non scalare molto bene in caso di conflitto elevato. In linea di principio, penso che sia ansible risolvere ciò avendo un object Condition per ogni ID attualmente bloccato, che è quindi la cosa che attendi / segnali. Ovviamente, se in pratica raramente si attendono più di un ID in un dato momento, non si tratta di un problema.

Steve,

il tuo codice proposto ha un sacco di problemi con la sincronizzazione. (Anche quello di Antonio).

Riassumere:

  1. È necessario memorizzare nella cache un object costoso.
  2. È necessario assicurarsi che mentre un thread sta eseguendo il recupero, un altro thread non tenta anche di recuperare lo stesso object.
  3. Per tutti i thread n, tutti i tentativi di ottenere l’object solo 1 object vengono sempre recuperati e restituiti.
  4. Quello per i thread che richiedono oggetti diversi che non si contendono l’un l’altro.

codice pseudo per farlo accadere (usando una ConcurrentHashMap come cache):

 ConcurrentMap> cache = new ConcurrentHashMap>; public Page getPage(Integer id) { Future myFuture = new Future(); cache.putIfAbsent(id, myFuture); Future actualFuture = cache.get(id); if ( actualFuture == myFuture ) { // I am the first w00t! Page page = getFromDataBase(id); myFuture.set(page); } return actualFuture.get(); } 

Nota:

  1. java.util.concurrent.Future è un’interfaccia
  2. java.util.concurrent.Future in realtà non ha un set (), ma guarda le classi esistenti che implementano Future per capire come implementare il tuo futuro (o usare FutureTask)
  3. Spingere il recupero effettivo su un thread di lavoro sarà quasi certamente una buona idea.