2013-02-14 12 views
7

Zamieściłem odpowiedź here gdzie kod wykazanie wykorzystania metody ConcurrentMapputIfAbsent przeczytać:lambdas i 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; 
} 

Główną wadą tego podejścia jest to, że trzeba stworzyć nowy obiekt, aby umieścić w mapa, czy będzie używana, czy nie. Może to mieć znaczący wpływ, jeśli obiekt jest ciężki.

Przyszło mi do głowy, że będzie to okazja do skorzystania z Lambdas. Nie pobrałem Java 8 lub czy będę w stanie, dopóki nie będzie ona oficjalna (polityka firmy), więc nie mogę tego przetestować, ale czy coś takiego będzie ważne i skuteczne?

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

Mam nadzieję, że do korzystania z lambda opóźnić ocenę new AtomicLong(0) dopóki nie zostanie ustalone, że rzeczywiście powinien zostać utworzony, ponieważ nie istnieje na mapie.

Jak widać, jest to bardziej zwięzłe i funkcjonalne.

Zasadniczo przypuszczam moje pytania są następujące:

  1. Będzie to działać?
  2. Czy też całkowicie błędnie zinterpretowałem lambdas?
  3. Może kiedyś coś takiego się stanie?
+2

W jaki sposób nie można pobrać Java 8 i przetestować jej samodzielnie? Czy twoja firma uniemożliwia ci zainstalowanie czegokolwiek (nawet do celów oceny) na twoim komputerze? A może wypróbujesz to na swojej osobistej? –

+0

@SimonLehmann - BTW - Po twoim komentarzu zainstalowałem Java 8 i nie tylko nie miał Lambdas (był dodatkowy, który miałem zainstalować, ale nie udało mi się tego obejść), ale DBVisualiser przestał działać - więc usunąłem go. Przepraszam. Być może w przyszłym roku. – OldCurmudgeon

Odpowiedz

6

UPDATE 2015-08-01

Sposób computeIfAbsent jak opisano poniżej jest rzeczywiście added to Java SE 8. Semantyka wydaje się być bardzo zbliżona do wersji przed wydaniem.

Ponadto, computeIfAbsent, wraz z całym stosem nowych metod domyślnych, został dodany do interfejsu Map. Oczywiście mapy ogólnie nie mogą obsługiwać aktualizacji atomowych, ale nowe metody znacznie zwiększają wygodę interfejsu API.


Co starasz się zrobić to dość rozsądne, ale niestety to nie działa w obecnej wersji ConcurrentMap. Jednak ulepszenie jest już w toku. Nowa wersja biblioteki współbieżności zawiera ConcurrentHashMapV8, która zawiera nową metodę computeIfAbsent. W ten sposób możesz zrobić dokładnie to, czego szukasz. Korzystanie z tej nowej metody, Twój przykład może zostać przepisany w następujący sposób:

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

W celu uzyskania dalszych informacji na temat ConcurrentHashMapV8 patrz Doug Lea initial announcement thread na liście mailingowej współbieżności-procentowej. Kilka wiadomości w wątku to a followup message, który pokazuje przykład bardzo podobny do tego, co próbujesz zrobić. (Zwróć jednak uwagę na starą składnię lambda. Ta wiadomość pochodzi z sierpnia 2011 roku.) A tutaj jest recent javadoc dla ConcurrentHashMapV8.

Ta praca ma być zintegrowana z Javą 8, ale nie jest jeszcze tak daleko, jak widzę. Nadal trwają prace nad nimi, nazwy i specyfikacje mogą się zmieniać, itp.

+0

Dziękuję - z wyjątkiem faktu, że będzie to nazywane "ConcurrentHashMapV8". Cóż za przerażający pomysł! – OldCurmudgeon

+1

Nie jestem pewien, czy nadal będzie on nazywał się CHMV8 przed faktycznym zintegrowaniem z Javą 8. Podejrzewam, że Doug Lea nazywa to CHMV8, więc może być używany w tym samym czasie co CHM w aplikacjach, testach, benchmarkach do porównania cele. Lea twierdzi, że ma to być zamiennik CHM. –

+0

To dobra wiadomość. Dzięki za szczegóły. – OldCurmudgeon

2

Niestety to nie jest takie proste. Istnieją dwa główne problemy związane z przedstawionym podejściem: 1. Typ mapy musiałby zmienić się z Map<String, AtomicLong> na Map<String, AtomicLongFunction> (gdzie AtomicLongFunction to jakiś interfejs funkcji, który ma jedną metodę, która nie przyjmuje argumentów i zwraca wartość AtomicLong). 2. Gdy pobierzesz element z mapy, musisz zastosować tę funkcję za każdym razem, aby uzyskać z niej AtomicLong. Powoduje to utworzenie nowej instancji za każdym razem, gdy ją pobierzesz, co prawdopodobnie nie jest tym, czego potrzebujesz.

Pomysł posiadania mapy, która uruchamia funkcję na żądanie w celu uzupełnienia brakujących wartości, jest jednak dobry, a w rzeczywistości biblioteka Guava Google ma mapę, która dokładnie to robi; zobacz ich MapMaker. W rzeczywistości, że kod będzie korzystać z Java wyrażeń lambda 8: zamiast

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

byłbyś w stanie napisać

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

lub

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

Jakoś po prostu wiedziałem, że moje rozwiązanie jest o wiele prostsze, niż mogłoby się stać. Więc efektywnie dodawałbym ** lambda ** do mapy, a nie ** wynik ** lambda ... no cóż. – OldCurmudgeon

2

AtomicLong nie jest naprawdę ciężki przedmiot . W przypadku obiektów cięższych rozważałbym leniwy serwer proxy i udostępniłbym mu lambdę, aby utworzyć obiekt w razie potrzeby.

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

Użyłem "AtomicLong" jako przykładowego posiadacza miejsca, naprawdę myślałem w kategoriach dużo cięższych przedmiotów. Wygląda na to, że jest skuteczny, ale tak naprawdę wiele rzeczy dzieje się w celu wdrożenia lenistwa, które i tak ma być wrodzone w lambda. – OldCurmudgeon

+0

Korzystanie z biblioteki Guava wydaje się łatwiejsze. Ale pisanie leniwych obiektów uniezależnia Cię od implementacji Map. Można także utworzyć ogólny obiekt wywołania Invocation, który delegowałby wszystkie wywołania metod, zobacz [Serwer proxy] (http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html). Ale masz rację, w większości przypadków byłaby to prawdziwa przesada. –

1

Należy pamiętać, że korzystanie z Java 8 ConcurrentHashMap jest całkowicie niepotrzebne, aby mieć wartości AtomicLong. Można bezpiecznie używać ConcurrentHashMap.merge:

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

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

To znacznie prostsze i znacznie szybciej.