2009-03-18 18 views
25

Powiel możliwe:
What is the best way to increase number of locks in javaSynchronizacja na wartość całkowitą

Przypuśćmy, że chcesz zablokować w oparciu o całkowitą wartość id. W takim przypadku istnieje funkcja, która pobiera wartość z pamięci podręcznej i pobiera dość drogie pliki do pamięci podręcznej, jeśli ich nie ma.

Istniejący kod nie jest zsynchronizowane i mogą powodować wiele pobierać operacji/przechowywanie:

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

Jakie chciałem zrobić to zsynchronizować pobierać na identyfikatorze, na przykład

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

Niestety to nie będzie działać, ponieważ 2 oddzielne połączenia mogą mieć taką samą wartość Integer id ale inny obiekt Integer, więc nie będą dzielić się zamek i brak synchronizacji nastąpi.

Czy istnieje prosty sposób na zapewnienie, że masz tę samą instancję typu Integer? Na przykład, to będzie działać:

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

Javadoc dla Integer.valueOf() zdaje się sugerować, że jesteś prawdopodobne, aby uzyskać ten sam przypadek, ale to nie wygląda gwarancji:

Powoduje zwrócenie instancji Integer reprezentującej określoną wartość typu int. razie wymagany jest nowy przypadek całkowita metoda ta powinna generalnie pierwszeństwo do konstruktora całkowitych (INT), a metoda ta może otrzymując znacznie lepsze w czasie i przestrzeni występ buforowanie często wystąpił o wartości.

Tak, wszelkie sugestie, w jaki sposób uzyskać wystąpienie Integer, które gwarantuje, że są takie same, inne niż bardziej skomplikowane rozwiązania, takie jak utrzymywanie WeakHashMap obiektów Lock z kluczem do int? (nic w tym złego, po prostu wydaje się, że musi istnieć oczywisty jeden-liniowy, niż mi brakuje).

+2

To pytanie "duplikat" i odpowiedź Eddiego są lepsze niż "oryginalne" pytanie! – arun

+0

Zobacz także http://stackoverflow.com/questions/6616141/java-threads-locking-on-a-specific-object w szczególności http://stackoverflow.com/a/27806218/32453 – rogerdpack

+0

Co z użyciem [AtomicInteger] (https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicInteger.html)? – GregT

Odpowiedz

43

Naprawdę nie chcesz synchronizować na Integer, ponieważ nie masz kontroli nad tym, które instancje są takie same i jakie instancje są różne. Java po prostu nie zapewnia takiej możliwości (chyba że używasz liczb całkowitych w małym zakresie), która jest niezawodna w różnych maszynach JVM. Jeśli naprawdę musisz zsynchronizować na Integer, musisz zachować mapę lub zestaw liczb całkowitych, aby zagwarantować, że otrzymujesz dokładnie taką instancję, jaką chcesz.

Lepiej byłoby stworzyć nowy obiekt, być może przechowywany w HashMap, który jest kluczowany przez Integer, aby zsynchronizować. Coś takiego:

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

private ConcurrentMap<Integer, Integer> locks = new ConcurrentHashMap<Integer, Integer>(); 

private Object getCacheSyncObject(final Integer id) { 
    locks.putIfAbsent(id, id); 
    return locks.get(id); 
} 

Aby wyjaśnić ten kod, wykorzystuje ConcurrentMap, który umożliwia korzystanie z putIfAbsent. Możesz to zrobić:

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

, ale wtedy poniesiesz (mały) koszt utworzenia obiektu dla każdego dostępu. Aby tego uniknąć, po prostu zapiszę samą Integer w Map. Co to osiąga? Dlaczego różni się to od samego użycia samego Integer?

Po wykonaniu get() od A Map klawisze są porównywane z equals() (lub co najmniej metoda jest równoznaczne z użyciem equals()). Dwie różne instancje Integer o tej samej wartości będą sobie równe. W związku z tym można przekazać dowolną liczbę różnych instancji Integer "new Integer(5)" jako parametr do getCacheSyncObject, a zawsze otrzymasz tylko pierwszą instancję, która została przekazana zawierającą tę wartość.

Istnieją powody, dla których możesz nie chcieć synchronizować na Integer ... możesz dostać się w zakleszczenia, jeśli wiele wątków synchronizuje się na obiektach Integer i w ten sposób nieświadomie używa tych samych blokad, gdy chcą używać różnych blokad. Możesz naprawić to ryzyko, używając wersji i tym samym ponosząc (bardzo) niewielki koszt każdego dostępu do pamięci podręcznej. Dzięki temu zagwarantujesz, że ta klasa będzie synchronizować obiekt, którego żadna inna klasa nie będzie synchronizować. Zawsze dobra rzecz.

+0

Osoba wdrażająca musiałaby rozwinąć to rozwiązanie, aby zsynchronizować mapę zamków i dodać pewien rodzaj licznika odwołań lub mechanizmu zwolnienia. – McDowell

+0

Tak, jeśli element zostanie zwolniony z pamięci podręcznej stron, należy zwolnić blokadę, która była używana dla tego elementu pamięci podręcznej strony. – Eddie

+0

Czy wewnątrz getCacheSyncObject jest wyścig? Czy synchronizacja ma wpływ na wynik połączenia lub inny obiekt do obliczenia wartości wyrażenia? Jest otwór, w którym może się skończyć synchronizacja na 2 różnych obiektach. Prawdopodobnie zadziała putIfAbsent z innego rozwiązania. – johnny

3

Integer.valueOf() zwraca tylko buforowane instancje dla ograniczonego zakresu. Nie określiłeś swojego zasięgu, ale generalnie to nie zadziała.

Jednakże zdecydowanie nie zaleca się stosowania tego podejścia, nawet jeśli podane wartości mieszczą się w prawidłowym zakresie. Ponieważ te buforowane instancje Integer są dostępne dla dowolnego kodu, nie można w pełni kontrolować synchronizacji, co może doprowadzić do zakleszczenia. To jest ten sam problem, który ludzie próbują zablokować na wyniku String.intern().

Najlepsza blokada to zmienna prywatna. Ponieważ tylko twój kod może się do niego odnosić, możesz zagwarantować, że nie wystąpią zakleszczenia.

Nawiasem mówiąc, użycie WeakHashMap również nie będzie działać. Jeśli instancja służąca jako klucz nie jest odwoływana, zostaną zebrane śmieci. A jeśli jest silnie odwołany, możesz go użyć bezpośrednio.

1

Co powiesz na ConcurrentHashMap z obiektami Integer jako kluczami?

+0

Tak, jak już wspomniałem, mógłbym użyć jakiejś mapy , gdzie obiekty byłyby prywatnymi blokadami. Byłem bardziej zainteresowany odkryciem, czy w java jest jakiś sposób zagwarantowania konkretnej instancji Integer. –

1

Można odszukać this code w celu utworzenia muteksu z identyfikatora. Kod został napisany dla String ID, ale można go było łatwo edytować dla obiektów Integer.

3

Używanie zsynchronizowanych na liczbach całkowitych brzmi bardzo źle według projektu.

Jeśli chcesz zsynchronizować każdy przedmiot indywidualnie tylko podczas pobierania/zapisywania, możesz utworzyć Zestaw i zapisać tam aktualnie zablokowane pozycje. Innymi słowy,

// this contains only those IDs that are currently locked, that is, this 
// will contain only very few IDs most of the time 
Set<Integer> 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(); 
     } 
    } 
} 

Najważniejsze to: nie ma pojedynczej linii kodu, która rozwiązuje ten problem dokładnie tak, jak tego potrzebujesz.

+0

+1 Zasadniczo podoba mi się to podejście. Jest tylko potencjalny problem, o którym wspominam w moim poście poniżej, że pod wysokim sporem oczekujące wątki dla WSZYSTKICH identyfikatorów są budzone, gdy tylko jeden ID stanie się dostępny. –

+0

To może nie być prawdziwy problem, jeśli nie masz zbyt wielu wątków. Ale jeśli tak jest, możesz użyć Object.notify() i użyć Object.wait (maxTime), aby upewnić się, że żaden rozmówca nie utknął kiedykolwiek. – Antonio

+0

Zgadzam się, że prawdopodobnie nie jest to zbyt duży problem przy niskim poziomie rywalizacji. Niestety, myślę, że wspomniane rozwiązanie może być gorsze ...! –

4

Użyj mapy bezpiecznej dla wątków, takiej jak ConcurrentHashMap. Umożliwi to bezpieczną manipulację mapą, ale użyje innej blokady do wykonania rzeczywistych obliczeń.W ten sposób można wykonywać wiele obliczeń jednocześnie z pojedynczą mapą.

Użyj ConcurrentMap.putIfAbsent, ale zamiast umieszczać rzeczywistą wartość, należy zamiast tego użyć konstrukcji Future z konstrukcją obliczeniowo-lekką. Prawdopodobnie implementacja FutureTask. Uruchom obliczenia, a następnie wynik, który wątek bezpiecznie zablokuje, dopóki nie zostanie wykonany.

1

Jak widać z różnych odpowiedzi, istnieje wiele sposobów na skórę ten kot:

  • Goetz i wsp za podejście prowadzenie cache FutureTasks działa całkiem dobrze w takich sytuacjach to tam, gdzie i tak coś "cache'ujesz", więc nie miej nic przeciwko budowaniu mapy obiektów FutureTask (a jeśli nie przeszkadzało ci powiększanie mapy, przynajmniej łatwo jest ją przycinać współbieżnie)
  • Ogólna odpowiedź "jak zablokować identyfikator", podejście przedstawione przez Antonio ma tę przewagę kapelusz jest oczywisty, gdy mapa zamków jest dodana do/usunięta z.

Może trzeba zwrócić uwagę potencjalnego problemu z Antonio realizacji, a mianowicie, że notifyAll() będzie obudzić wątki czekające na wszystkich identyfikatorów kiedy jeden z nich stają się dostępne, który nie może skalują się bardzo dobrze w warunkach dużej rywalizacji. Zasadniczo myślę, że możesz to naprawić, mając obiekt warunku dla każdego aktualnie zablokowanego identyfikatora, który jest tym, na co czekasz/sygnał. Oczywiście, jeśli w praktyce rzadko można oczekiwać więcej niż jednego identyfikatora w danym momencie, nie stanowi to problemu.

1

Steve

proponowanego kod ma kilka problemów z synchronizacją. (Antonio też tak robi).

Podsumowując:

  1. trzeba buforować drogiego obiekt.
  2. Należy upewnić się, że podczas pobierania jednego wątku inny wątek nie próbuje pobrać tego samego obiektu.
  3. To dla n-wątków wszystkie próby uzyskania obiektu tylko 1 obiektu są zawsze pobierane i zwracane.
  4. To dla wątków żądających różnych obiektów, których się nie zwalczają.

pseudokod aby tak się stało (stosując ConcurrentHashMap jako cache):

ConcurrentMap<Integer, java.util.concurrent.Future<Page>> cache = new ConcurrentHashMap<Integer, java.util.concurrent.Future<Page>>; 

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

Uwaga:

  1. java.util.concurrent.Future jest interfejsem
  2. java .util.concurrent.Future w rzeczywistości nie ma zestawu(), ale przygląda się istniejącym klasom, które wdrażają Future, aby zrozumieć, jak wdrożyć własną przyszłość (lub użyć FutureTask).
  3. Przesłanie rzeczywistego pobierania do wątku roboczego prawie na pewno będzie dobrym pomysłem.
Powiązane problemy