2013-02-14 2 views
7

ho postato una risposta here in cui il codice che dimostra l'uso del metodo di ConcurrentMapputIfAbsent leggere:lambda e putIfAbsent

ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>(); 

public long addTo(String key, long value) { 
    // The final value it became. 
    long result = value; 
    // Make a new one to put in the map. 
    AtomicLong newValue = new AtomicLong(value); 
    // Insert my new one or get me the old one. 
    AtomicLong oldValue = map.putIfAbsent(key, newValue); 
    // Was it already there? Note the deliberate use of '!='. 
    if (oldValue != newValue) { 
    // Update it. 
    result = oldValue.addAndGet(value); 
    } 
    return result; 
} 

Lo svantaggio principale di questo approccio è che si deve creare un nuovo oggetto da mettere in mappa se sarà usato o meno. Questo può avere un effetto significativo se l'oggetto è pesante.

Mi è venuto in mente che questa sarebbe stata l'occasione per usare Lambdas. Non ho scaricato Java 8 e non sarò in grado di farlo fino a quando non sarà ufficiale (politica aziendale), quindi non posso testarlo, ma una cosa del genere sarebbe valida ed efficace?

public long addTo(String key, long value) { 
    return map.putIfAbsent(key,() -> new AtomicLong(0)).addAndGet(value); 
} 

spero di usare la lambda per ritardare la valutazione del new AtomicLong(0) fino a quando non è in realtà determinata che dovrebbe essere creato perché non esiste nella mappa.

Come potete vedere, questo è molto più succinto e funzionale.

Essenzialmente suppongo le mie domande sono:

  1. Sarà questo lavoro?
  2. Oppure ho interpretato erroneamente lambda?
  3. Potrebbe qualcosa del genere questo lavoro un giorno?
+2

In che modo non è possibile scaricare Java 8 e testarlo da soli? La tua azienda ti impedisce di installare nulla (anche a scopo di valutazione) sul tuo computer di lavoro? Che ne dici di provarlo su quello personale allora? –

+0

@SimonLehmann - BTW - Dopo il tuo commento ho installato Java 8 e non solo non ha Lambdas (c'era un extra che dovevo installare ma non ci sono riuscito) ma DBVisualiser ha smesso di funzionare - quindi l'ho rimosso. Scusate. Forse l'anno prossimo. – OldCurmudgeon

risposta

6

UPDATE 2015-08-01

Procedimento computeIfAbsent come descritto di seguito è stato effettivamente added to Java SE 8. La semantica sembra essere molto vicina alla versione pre-release.

Inoltre, computeIfAbsent, insieme con un mucchio di nuovi metodi predefiniti, è stata aggiunta all'interfaccia Map. Naturalmente, le mappe in generale non supportano gli aggiornamenti atomici, ma i nuovi metodi aggiungono una notevole convenienza all'API.


cosa si sta cercando di fare è abbastanza ragionevole, ma purtroppo non funziona con la versione corrente di ConcurrentMap. Un miglioramento è in arrivo, tuttavia. La nuova versione della libreria di concorrenza include ConcurrentHashMapV8 che contiene un nuovo metodo computeIfAbsent. Questo ti permette di fare esattamente quello che stai cercando di fare. Con questo nuovo metodo, il tuo esempio potrebbe essere riscritto come segue:

public long addTo(String key, long value) { 
    return map.computeIfAbsent(key,() -> new AtomicLong(0)).addAndGet(value); 
} 

Per ulteriori informazioni sulla ConcurrentHashMapV8, vedere Doug Lea di initial announcement thread sulla mailing list concorrenza interesse. Diversi messaggi lungo il thread sono a followup message che mostra un esempio molto simile a quello che stai cercando di fare. (Nota, tuttavia, la vecchia sintassi lambda. Questo messaggio è stato dall'agosto del 2011 dopo tutto.) Ed ecco recent javadoc per ConcurrentHashMapV8.

Questo lavoro è stato progettato per essere integrato in Java 8, ma non è ancora il più lontano possibile. Inoltre, questo è ancora in corso, i nomi e le specifiche potrebbero cambiare, ecc.

+0

Grazie - tranne per il fatto che verrà chiamato 'ConcurrentHashMapV8'. Che idea terrificante !! – OldCurmudgeon

+1

Non sono sicuro che sarà ancora chiamato CHMV8 per il momento in cui è effettivamente integrato in Java 8. Sospetto che Doug Lea lo chiami CHMV8 in modo che possa essere utilizzato contemporaneamente a CHM in app, test, benchmark per il confronto scopi. Lea dice che è destinato a sostituire il CHM. –

+0

Questa è una buona notizia. Grazie per il dettaglio. – OldCurmudgeon

2

Sfortunatamente non è così facile. Ci sono due problemi principali con l'approccio che hai delineato: 1. Il tipo di mappa dovrebbe cambiare da Map<String, AtomicLong> a Map<String, AtomicLongFunction> (dove AtomicLongFunction è una qualche interfaccia di funzione che ha un unico metodo che non accetta argomenti e restituisce un AtomicLong). 2. Quando si recupera l'elemento dalla mappa, è necessario applicare la funzione ogni volta per ottenere da AtomicLong. Ciò comporterebbe la creazione di una nuova istanza ogni volta che la recuperai, il che probabilmente non è ciò che volevi.

L'idea di avere una mappa che esegue una funzione su richiesta per riempire i valori mancanti è buona, però, e in effetti la libreria Guava di Google ha una mappa che fa esattamente questo; vedere il loro MapMaker. In realtà che il codice sarebbe beneficiare di Java espressioni lambda 8: invece di

ConcurrentMap<Key, Graph> graphs = new MapMaker() 
     .concurrencyLevel(4) 
     .weakKeys() 
     .makeComputingMap(
      new Function<Key, Graph>() { 
      public Graph apply(Key key) { 
       return createExpensiveGraph(key); 
      } 
      }); 

si sarebbe in grado di scrivere

ConcurrentMap<Key, Graph> graphs = new MapMaker() 
     .concurrencyLevel(4) 
     .weakKeys() 
     .makeComputingMap((Key key) -> createExpensiveGraph(key)); 

o

ConcurrentMap<Key, Graph> graphs = new MapMaker() 
     .concurrencyLevel(4) 
     .weakKeys() 
     .makeComputingMap(this::createExpensiveGraph); 
+0

In qualche modo sapevo che la mia soluzione era molto più semplice che sarebbe finita. Quindi aggiungerei la ** lambda ** alla mappa piuttosto che il ** risultato ** del lambda ... vabbè. – OldCurmudgeon

2

AtomicLong non è davvero un oggetto pesante . Per gli oggetti più pesanti considererei un proxy pigro e fornirò un lambda a quello per creare l'oggetto se necessario.

class MyObject{ 
    void doSomething(){} 
} 

class MyLazyObject extends MyObject{ 
    Funktion create; 
    MyLazyObject(Funktion create){ 
     this.create = create; 
    } 
    MyObject instance; 
    MyObject getInstance(){ 
     if(instance == null) 
      instance = create.apply(); 
     return instance; 
    } 
    @Override void doSomething(){getInstance().doSomething();} 
} 

public long addTo(String key, long value) { 
    return map.putIfAbsent(key, new MyLazyObject(() -> new MyObject(0))); 
} 
+0

Ho usato un 'AtomicLong' come esempio di segnaposto, stavo davvero pensando in termini di oggetti molto più pesanti. Questo sembra efficace ma c'è un sacco di cose in corso per implementare la pigrizia che dovrebbe essere innata in un lambda comunque. – OldCurmudgeon

+0

L'uso della libreria Guava sembra più semplice. Ma scrivere oggetti pigri ti rende indipendente dalle implementazioni della mappa. È anche possibile creare un InvocationHandler generico che delegherebbe tutte le chiamate di metodi, vedere [Proxy] (http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html). Ma hai ragione, nella maggior parte dei casi sarebbe una vera e propria esagerazione. –

1

Si noti che utilizzando Java 8 ConcurrentHashMap non è necessario avere valori AtomicLong. È possibile utilizzare in modo sicuro ConcurrentHashMap.merge:

ConcurrentMap<String, Long> map = new ConcurrentHashMap<String, Long>(); 

public long addTo(String key, long value) { 
    return map.merge(key, value, Long::sum); 
} 

E 'molto più semplice e anche significativamente più veloce.