2010-02-17 23 views
13

W naszym systemie mamy metodę, która będzie popracować kiedy to się nazywa z określonym ID:Java: Synchronizacja na elementach pierwotnych?

public void doWork(long id) { /* ... */ } 

Teraz ta praca może być wykonywana równolegle dla różnych identyfikatorów, ale jeśli metoda jest wywoływana z ten sam identyfikator przez 2 wątki, jeden wątek powinien blokować, dopóki nie zostanie ukończony.

Najprostszym rozwiązaniem jest posiadanie mapy mapującej z Long ID na dowolny obiekt, który możemy zablokować. Jednym z problemów, które przewiduję, jest to, że możemy mieć mnóstwo identyfikatorów w systemie, a mapa będzie nadal rosła każdego dnia.

Idealnie, myślę, że potrzebujemy systemu, w którym każdy wątek pobierze obiekt blokady, zablokuje, gdy będzie to możliwe, wykona pracę, a następnie zasygnalizuje, że skończyliśmy z zamkiem. Jeśli jest jasne, że nikt inny nie używa tej konkretnej blokady, to bezpiecznie usuń ją z mapy zamka, aby zapobiec wyciekowi pamięci.

Wyobrażam sobie, że to dość powszechny scenariusz, więc mam nadzieję, że istnieje istniejące rozwiązanie. Ktoś o tym wie?

+0

W jaki sposób są generowane te numery identyfikacyjne? Jeśli jest jakiś pośrednik w tych identyfikatorach, po prostu rozdaj obiekty zamiast prymitywów i możesz ich użyć do blokowania/synchronizacji. –

+0

Zobacz także http://stackoverflow.com/q/6616141/32453 – rogerdpack

Odpowiedz

15

Już jakiś czas temu wymyśliłem coś takiego. Nazywam to zamkiem klasy równoważności, co oznacza, że ​​blokuje wszystkie rzeczy, które są równe danej rzeczy. Możesz go uzyskać from my github i używać go z zastrzeżeniem licencji Apache 2, jeśli chcesz, lub po prostu przeczytać i zapomnieć!

+0

+1 za zapewnienie darmowej implementacji. –

+2

I warte każdego grosza. ;) –

0

Czy nie wystarczy użyć SynchronizedHashMap lub Collections.synchronizedMap (Map m) z pakietu java.util.concurrent zamiast zwykłej HashMap, w której wywołania dotyczące pobierania i wstawiania nie są zsynchronizowane?

coś takiego:

Map<Long,Object> myMap = new HashMap<Long,Object>(); 
Map<Long,Object> mySyncedMap=Collections.synchronizedMap(myMap); 
4

Powiedziałbym, że jesteś już dość daleko do konieczności rozwiązania. Stwórz LockManager, który leniwie i referencyjnie zarządza tymi zamkami. Następnie używać go w doWork:

public void doWork(long id) { 
    LockObject lock = lockManager.GetMonitor(id); 
    try { 
     synchronized(lock) { 
      // ... 
     } 
    } finally { 
     lock.Release(); 
    } 
} 
+0

Zacząłem poprawianie kodu, ale potem - to też nie jest - Java do naprawienia. (Obiekt nie ma "wydania") – Bozho

+0

Tak, to jest bardzo podobne do tego, co mam teraz, ale nie ufam sobie wystarczająco, aby zrobić to dobrze. Dwa brakujące fragmenty to implementacje klas Lock i LockManager. –

+0

Dla mnie brzmi to jak realne rozwiązanie. Zastąp 'Object' inną niestandardową klasą. –

0

Przedwczesna optymalizacja jest korzeniem zła

Spróbuj z (zsynchronizowany) mapie.

Może, jeśli będzie zbyt duży, możesz regularnie usuwać jego zawartość.

+2

Nie nazwałbym tego przedwczesnym. Wiemy, że każdego dnia używa się tysięcy unikalnych identyfikatorów. Nie chcę opróżniać mapy, ponieważ jest prawdopodobne, że przynajmniej jedna blokada będzie używana. Zastanawiam się nad użyciem mapy, na której najstarszy wpis jest usuwany, gdy mapa jest pełna, ale nawet wtedy nie ma gwarancji, że blokada nie jest używana. –

-2

Proponuję użyć narzędzi z java.util.concurrent, szczególnie klasy AtomicLong. Zobacz related javadoc

+0

Nie sądzę, że istnieje jedna konkretna klasa, która mi pomoże. Szukam strategii, jak wykorzystać te kawałki razem. –

+0

Proszę przeczytać stronę: pomoże zrozumieć, jak z niego korzystać –

+1

Philippe, on nie potrzebuje atomowej długości. Potrzebuje wielu różnych zamków. –

4

Na początek:

  1. Nie można zablokować na prymitywne i
  2. Nie blokować na długim jeśli nie jesteś ostrożny, jak je skonstruować. Długie wartości tworzone przez autoboxing lub Long.valueOf() w pewnym zakresie są gwarantowane w ten sam sposób w całej maszynie JVM, co oznacza, że ​​inne wątki mogą blokować ten sam dokładny obiekt długi i powodować przesłuchanie. Może to być subtelny błąd współbieżności (podobny do blokowania ciągów internowanych).

Mówisz tutaj o konfiguracji blokującej paski. Jednym z końców kontinuum jest pojedynczy gigantyczny zamek dla wszystkich identyfikatorów, który będzie łatwy i bezpieczny, ale nie będzie współbieżny. Drugi koniec to blokada na id, która jest łatwa (do pewnego stopnia) i bezpieczna i bardzo współbieżna, ale może wymagać dużej liczby "zablokowanych obiektów" w pamięci (jeśli jeszcze ich nie masz). Gdzieś w środku jest pomysł stworzenia blokady dla szeregu identyfikatorów - pozwala to dostosować współbieżność w zależności od środowiska i dokonywać wyborów dotyczących kompromisu między pamięcią a współbieżnością.Aby osiągnąć ten cel, można użyć

ConcurrentHashMap, ponieważ CHM składa się z segmentów (pod-map) i jest jedna blokada na segment. Daje to współbieżność równą liczbie segmentów (domyślnie 16, ale jest konfigurowalna).

Istnieje kilka innych możliwych rozwiązań w zakresie partycjonowania przestrzeni identyfikacyjnej i tworzenia zestawów zamków, ale masz prawo być wrażliwym na problemy związane z czyszczeniem i wyciekami pamięci - dbanie o to przy zachowaniu współbieżności jest trudnym zadaniem. Musisz użyć jakiegoś odnośnika, licząc na każdy zamek i ostrożnie zarządzać eksmisją starych zamków, aby uniknąć eksmisji zamka, który jest w trakcie zamykania. Jeśli wybierzesz tę trasę, użyj ReentrantLock lub ReentrantReadWriteLock (i nie zsynchronizuj się z obiektami), ponieważ pozwala to jawnie zarządzać zamkiem jako obiektem i korzystać z dodatkowych dostępnych na nim metod.

Istnieje również kilka rzeczy na ten temat i przykład StripedMap w Java Concurrency in Practice sekcja 11.4.3.

+0

Heh, miałem nadzieję, że się pojawisz. Czy nie ma lepszego sposobu na odwzorowanie mojego zakresu identyfikatorów do określonej liczby zamków? Idealnie byłoby, gdyby każdy identyfikator miał swój własny zamek, ale te blokady zostaną usunięte, gdy jestem pewien, że nie są już potrzebne. W ten sposób mogę uzyskać maksymalną współbieżność, jednocześnie upewniając się, że użycie pamięci nie rośnie w nieskończoność. –

+0

To jest dokładnie to, co Ehcache z Terracotta robi wewnętrznie (z klastrowymi zamkami). Mogę powiedzieć, że równoważenie ograniczeń dotyczących poprawności współbieżności, wydajności i użycia pamięci jest trudnym problemem. Chciałbym mieć idealne rozwiązanie w przestrzeni Ehcache do wskazania tutaj, ale nie widzę (jeszcze) jednego, głównie dlatego, że wewnętrzne blokady nie są ujawnione w interfejsie API Ehcache -> może się zdarzyć w przyszłości. –

+0

'ConcurrentHashMap' może używać mniej blokad, ale nie musisz go trzymać przez cały czas trwania wywołania' doWork (id) '. – Matthew

6

Można wypróbować następujące trochę „siekać”

String str = UNIQUE_METHOD_PREFIX + Long.toString(id); 
synchornized(str.intern()) { .. } 

która jest 100% gwarancją zwrotu tej samej instancji.

UNIQUE_METHOD_PREFIX, może być ustalony na stałym poziomie, lub mogą być uzyskane przy użyciu:

StackTraceElement ste = Thread.currentThread().getStackTrace()[0]; 
String uniquePrefix = ste.getDeclaringClass() + ":" +ste.getMethodName(); 

który zagwarantuje, że zamek się dzieje tylko na tej precyzyjnej metody. To w celu uniknięcia zakleszczeń.

+0

Dobry pomysł. Fajny hack. –

+0

Zastanawiałem się nad tym, ale czy nie powoduje to przecieku pamięci w wewnętrznym buforze Long lub String? –

+1

Zły hack, Long.valueOf może za każdym razem zwrócić inny obiekt, a użycie string.intern() powoduje utratę przestrzeni, a każdy inny kod wykorzystujący łańcuchy jako blokadę może powodować zakleszczenia! – josefx

0

Można by utworzyć listę lub zestaw aktywnych identyfikatorów i używać czekać i powiadomić:

List<Long> working; 
public void doWork(long id) { 
synchronized(working) 
{ 
    while(working.contains(id)) 
    { 
     working.wait(); 
    } 
    working.add(id)//lock 
} 
//do something 
synchronized(working) 
{ 
    working.remove(id);//unlock 
    working.notifyAll(); 
} 
} 

Problemy rozwiązywane:

  • tylko wątki z tym samym identyfikatorem poczekajmy, wszyscy inni są współbieżne
  • Brak wycieku pamięci jako "zamki" (Long) zostaną usunięte podczas odblokowania
  • Działa z autoboxing

Problemy tam:

  • podczas/notifyAll może spowodować utratę wydajności z dużą liczbą wątków
  • Nie reentrant
0

tym miejscu chciałbym użyć mapy canonicalizing, która ma swoje long wejście i zwraca obiekt kanoniczny Long, którego można następnie użyć do synchronizacji. Pisałem o kanonizowaniu map here; po prostu wymień String przez Long (i aby ułatwić sobie życie, pozwól mu wziąć jako parametr long).

Gdy masz mapę canonicalizing, można napisać kod blokady strzeżone tak:

Long lockObject = canonMap.get(id); 
synchronized (lockObject) 
{ 
    // stuff 
} 

canonicalizing map by upewnić się, że jest taka sama lockObject wrócił do tego samego identyfikatora. Jeśli nie ma aktywnych odniesień do lockObject, będą one kwalifikowały się do zbierania śmieci, więc nie będziesz zapełniać pamięci niepotrzebnymi obiektami.

8

Możesz spróbować czegoś z ReentrantLock, tak, że masz Map<Long,Lock>. Teraz po lock.release() Możesz przetestować lock.hasQueuedThreads(). Jeśli to zwróci wartość false, możesz usunąć ją z mapy.

Powiązane problemy