Mam proces A, który zawiera tabelę w pamięci z zestawem rekordów (rekord A, rekord B itp.)
Teraz ten proces może uruchomić wiele wątków, które mają wpływ na rekordy, a czasami możemy mieć 2 wątki próbujące uzyskać dostęp do tego samego rekordu - należy odrzucić tę sytuację. W szczególności, jeśli rekord jest ZABLOKOWANY przez jeden wątek, chcę, aby inny wątek został przerwany (nie chcę BLOKOWAĆ ani CZEKAĆ).
Obecnie robię coś takiego:
synchronized(record)
{
performOperation(record);
}
Ale to sprawia mi problemy ... ponieważ podczas gdy Process1 wykonuje operację, jeśli Process2 wchodzi, blokuje / czeka na zsynchronizowaną instrukcję, a po zakończeniu Process1 wykonuje operację. Zamiast tego chcę coś takiego:
if (record is locked)
return;
synchronized(record)
{
performOperation(record);
}
Jakieś wskazówki, jak można to osiągnąć? Każda pomoc byłaby bardzo mile widziana. Dzięki,
źródło
Spójrz na obiekty Lock wprowadzone w pakietach współbieżności Java 5.
na przykład
Lock lock = new ReentrantLock() if (lock.tryLock()) { try { // do stuff using the lock... } finally { lock.unlock(); } } ...
Obiekt ReentrantLock zasadniczo robi to samo, co tradycyjny
synchronized
mechanizm, ale ma większą funkcjonalność.EDYCJA: Jak zauważył Jon,
isLocked()
metoda informuje Cię w tym momencie , a następnie te informacje są nieaktualne. Metoda tryLock () zapewni bardziej niezawodne działanie (pamiętaj, że możesz jej używać również z limitem czasu)EDYCJA # 2: Przykład zawiera teraz
tryLock()/unlock()
dla przejrzystości.źródło
Znalazłem to, możemy użyć
Thread.holdsLock(Object obj)
do sprawdzenia, czy obiekt jest zablokowany:Zwróć uwagę, że
Thread.holdsLock()
zwraca,false
jeśli blokada jest utrzymywana przez coś, a wątek wywołujący nie jest wątkiem, który utrzymuje blokadę.źródło
Chociaż powyższe podejście przy użyciu obiektu Lock jest najlepszym sposobem na zrobienie tego, jeśli musisz mieć możliwość sprawdzenia blokowania za pomocą monitora, można to zrobić. Jednak zawiera ostrzeżenie dotyczące zdrowia, ponieważ technika ta nie jest przenośna na maszyny wirtualne inne niż Oracle Java i może się zepsuć w przyszłych wersjach maszyn wirtualnych, ponieważ nie jest obsługiwanym publicznym interfejsem API.
Oto jak to zrobić:
private static sun.misc.Unsafe getUnsafe() { try { Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); return (Unsafe) field.get(null); } catch (Exception e) { throw new RuntimeException(e); } } public void doSomething() { Object record = new Object(); sun.misc.Unsafe unsafe = getUnsafe(); if (unsafe.tryMonitorEnter(record)) { try { // record is locked - perform operations on it } finally { unsafe.monitorExit(record); } } else { // could not lock record } }
Moją radą byłoby użycie tego podejścia tylko wtedy, gdy nie możesz refaktoryzować kodu w celu wykorzystania do tego obiektów java.util.concurrent Lock i jeśli używasz maszyny wirtualnej Oracle.
źródło
Chociaż odpowiedzi Lock są bardzo dobre, pomyślałem, że opublikuję alternatywę przy użyciu innej struktury danych. Zasadniczo różne wątki chcą wiedzieć, które rekordy są zablokowane, a które nie. Jednym ze sposobów jest śledzenie zablokowanych rekordów i upewnianie się, że struktura danych ma odpowiednie niepodzielne operacje do dodawania rekordów do zablokowanego zestawu.
Jako przykładu użyję CopyOnWriteArrayList, ponieważ jest to mniej „magiczne” dla ilustracji. CopyOnWriteArraySet jest bardziej odpowiednią strukturą. Jeśli masz średnio wiele rekordów zablokowanych w tym samym czasie, implementacje te mogą mieć wpływ na wydajność. Prawidłowo zsynchronizowany zestaw HashSet również zadziała, a blokady są krótkie.
Zasadniczo kod użycia wyglądałby tak:
CopyOnWriteArrayList<Record> lockedRecords = .... ... if (!lockedRecords.addIfAbsent(record)) return; // didn't get the lock, record is already locked try { // Do the record stuff } finally { lockedRecords.remove(record); }
Dzięki temu nie musisz zarządzać blokadą dla każdego rekordu i zapewnia jedno miejsce, w którym z jakiegoś powodu konieczne jest wyczyszczenie wszystkich blokad. Z drugiej strony, jeśli kiedykolwiek będziesz mieć więcej niż kilka rekordów, prawdziwy zestaw HashSet z synchronizacją może działać lepiej, ponieważ odnośniki dodawania / usuwania będą miały wartość O (1) zamiast liniowej.
Po prostu inny sposób patrzenia na rzeczy. Zależy to tylko od rzeczywistych wymagań dotyczących gwintowania. Osobiście użyłbym Collection.synchronizedSet (new HashSet ()), ponieważ będzie to naprawdę szybkie ... jedyną konsekwencją jest to, że wątki mogą dawać wyniki, gdy inaczej by tego nie robiły.
źródło
Lock
klasie.Innym rozwiązaniem jest użycie limitów czasu (jeśli nie miałeś szans na udzielenie odpowiedzi). tj. poniżej jeden zwróci wartość null po 1 sekundzie zawieszenia:
ExecutorService executor = Executors.newSingleThreadExecutor(); //create a callable for the thread Future<String> futureTask = executor.submit(new Callable<String>() { @Override public String call() throws Exception { return myObject.getSomething(); } }); try { return futureTask.get(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException | ExecutionException | TimeoutException e) { //object is already locked check exception type return null; }
źródło
Dzięki za to pomogło mi rozwiązać problem wyścigu. Zmieniłem go trochę, żeby nosić zarówno pasek, jak i szelki.
Oto moja propozycja POPRAWY przyjętej odpowiedzi:
Możesz zapewnić sobie bezpieczny dostęp do
tryLock()
metody, wykonując coś takiego:Lock localLock = new ReentrantLock(); private void threadSafeCall() { boolean isUnlocked = false; synchronized(localLock) { isUnlocked = localLock.tryLock(); } if (isUnlocked) { try { rawCall(); } finally { localLock.unlock(); } } else { LOGGER.log(Level.INFO, "THANKS! - SAVED FROM DOUBLE CALL!"); } }
Pozwoliłoby to uniknąć sytuacji, w której możesz otrzymać dwa połączenia
tryLock()
prawie w tym samym czasie, co spowodowałoby, że zwrot byłby potencjalnie pełen wątpliwości. Chciałbym teraz, jeśli się mylę, to może skończyło mi się tutaj ostrzeżenie. Ale hej! Mój występ jest teraz stabilny :-) ..Przeczytaj więcej o moich problemach z programowaniem na moim blogu .
źródło