2012-05-22 27 views
14

Potrzebuję ReadWriteLock, który NIE jest reentrant, ponieważ blokada może zostać zwolniona przez inny wątek niż ten, który go uzyskał. (Zdałem sobie z tego sprawę, gdy zacząłem sporadycznie przechwytywać IllegalMonitorStateException.)Czy istnieje nieczytelny ReadWriteLock, którego mogę użyć?

Nie jestem pewien, czy termin ten jest prawidłowy. ReentrantLock pozwala, aby wątek, który obecnie trzyma, blokuje, aby go ponownie zdobyć. NIE chcę tego zachowania, dlatego nazywam to "nierealnym".

Kontekst polega na tym, że mam serwer gniazd przy użyciu puli wątków. NIE ma wątku na połączenie. Żądania mogą być obsługiwane przez różne wątki. Połączenie klienta może wymagać zablokowania w jednym żądaniu i odblokowania w innym żądaniu. Ponieważ żądania mogą być obsługiwane przez różne wątki, muszę mieć możliwość blokowania i odblokowywania w różnych wątkach.

Założę dla tego pytania, że ​​muszę pozostać przy tej konfiguracji i że naprawdę muszę zablokować i odblokować w różnych żądaniach, a zatem prawdopodobnie różnych wątków.

Jest to ReadWriteLock, ponieważ muszę zezwolić na wiele "czytników" LUB na wyłączne "pisarz".

Wygląda na to, że można to napisać przy użyciu programu AbstractQueuedSynchronizer, ale obawiam się, że jeśli sam go napiszę, popełnię subtelny błąd. Mogę znaleźć różne przykłady użycia AbstractQueuedSynchronizer, ale nie ReadWriteLock.

Mogę wziąć źródło OpenJDK ReentrantReadWriteLock i spróbować usunąć element ponownie występujący, ale znowu obawiam się, że nie zrozumiałbym tego poprawnie.

Sprawdziłem w Guava i Apache Commons, ale nie znalazłem nic odpowiedniego. Apache Commons ma RWLockManager, który może zrobić to, czego potrzebuję, ale nie jestem pewien i wydaje się bardziej skomplikowany niż potrzebuję.

Odpowiedz

24

Semafor umożliwia różne wątki do wykonywania i wydawania zezwoleń. Wyjątkowy zapis jest równoznaczny z posiadaniem wszystkich zezwoleń, ponieważ wątek czeka, aż wszystkie zostaną zwolnione, a inne wątki nie mogą uzyskać dodatkowych zezwoleń.

final int PERMITS = Integer.MAX_VALUE; 
Semaphore semaphore = new Semaphore(PERMITS); 

// read 
semaphore.acquire(1); 
try { ... } 
finally { 
    semaphore.release(1); 
} 

// write 
semaphore.acquire(PERMITS); 
try { ... } 
finally { 
    semaphore.release(PERMITS); 
} 
+0

Dzięki, to brzmi jak może to wystarczyć. Czytanie dokumentów dla Semafor brzmi tak, jakbym musiał określić opcję "fair". "Ta klasa zapewnia również wygodne metody uzyskiwania i wydawania wielu zezwoleń naraz.Zwróć się do zwiększonego ryzyka nieokreślonego odroczenia, gdy metody te są stosowane bez prawdziwej uczciwości." –

+0

I widzę, że Semafor jest zaimplementowany (przynajmniej w OpenJDK) z AbstractQueuedSynchronizer –

+0

Awesome! Znajduję się w tej samej łodzi (potrzebuję blokady r/w za wielowątkowym/połączonym serwerem oszczędnościowym). To pytanie i odpowiedź są właśnie tym, czego potrzebowałem! Dzięki! – akagixxer

5

Nie jestem pewien, czy rozumiem pytanie. Nie-reentrantowy ReadWriteLock jest oksymoronem IMO, który podaje słowo "reentrant" has specific meaning w języku ojczystym. Musi być użyteczny z wielu wątków, inaczej nie zadziała. Jest tylko ReentrantReadWriteLock, który implementuje ReadWriteLock w JDK Java 6, które widzę. Z pewnością wiele odczytów może jednocześnie zablokować ReadWriteLock, ale użycie słowa "powtórka" w tym kontekście jest mylące.

Co należy zrobić, to mieć dobre wzorce blokowania i odblokowywania kodu. Na przykład zawsze używaj bloków try {} finally, aby mieć pewność, że otrzymasz odblokowanie.

ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); 
Lock readLock = readWriteLock.readLock(); 
... 
readLock.lock(); 
try { 
    ... // do stuff with the data 
} finally { 
    readLock.unlock(); 
} 

Sugestia, że ​​jeden wątek będzie lock() i inny unlock() nie jest tak jak powinno być za pomocą ReadWriteLock. To zły wzór.

wygląda to może być napisane przy użyciu AbstractQueuedSynchronizer

Ale to by cierpieć z tymi samymi problemami, które występują obecnie. Jeśli nie korzystasz z niego w odpowiedni sposób, nadal będziesz mieć dostęp do wątków mających dostęp do chronionego Collection poza blokadą.

zacząłem się IllegalMonitorStateException przerwami

jestem nie byłoby szybkie winę swoją ReadWriteLock do tego. Podejrzewam raczej, że masz jakiś kod, który jest , a nie blokowania kolekcji lub, że przez przypadek modyfikujesz kolekcję w blokadzie odczytu.

+3

Świetna odpowiedź. Jeśli twoje blokady są odblokowywane w innym wątku, nie stosujesz ich poprawnie. – derekerdmann

+3

Nierezydent może być złym terminem. ReentrantLock pozwala, aby wątek, który obecnie trzyma, blokuje, aby go ponownie zdobyć. NIE chcę tego zachowania, dlatego nazwałem je "nierealnym". Kontekst polega na tym, że mam serwer gniazd wykorzystujący wiele pul wątków. NIE ma wątku na połączenie. Żądania mogą być obsługiwane przez różne wątki. Połączenie klienta może wymagać zablokowania w jednym żądaniu i odblokowania w innym żądaniu. Ponieważ żądania mogą być obsługiwane przez różne wątki, muszę mieć możliwość blokowania i odblokowywania w różnych wątkach. –

+1

To wydaje się zbyt skomplikowane @Andrew. Możesz rozważyć wykonanie jednego wątku na połączenie, chyba że planujesz mieć 10 000 połączeń jednocześnie. Jeśli musisz pozostać przy aktualnej konfiguracji, to zablokowałbym transakcję pojedynczego klienta. Nie posiadałbym blokady odczytu, a następnie następnemu klientowi trxn obsługiwanemu przez następny wątek, by go odblokować.Nie wiedząc więcej o twoim programie, trudno niestety dalej pomóc. – Gray

1

Nie do końca pewny, czego potrzebujesz, szczególnie. dlaczego powinien to być blokada zapisu do odczytu, ale jeśli masz zadanie, które musi być obsługiwane przez wiele wątków i nie chcesz, aby był on przetwarzany/dostępny jednocześnie, użyłbym w rzeczywistości ConcurrentMap (itd.).

Możesz usunąć zadanie z mapy lub zastąpić je specjalnym "obiektem blokady", aby wskazać, że jest zablokowane. Możesz przywrócić zadanie ze zaktualizowanym stanem do mapy, aby umożliwić przejęcie innego wątku, lub możesz przekazać zadanie bezpośrednio do następnego wątku i pozwolić mu na zamianę zadania na mapę.

+0

Więc polecasz, żebym zaimplementował mój własny ReadWriteLock używając czegoś takiego jak ConcurrentMap? Nie jestem pewien, czy byłoby łatwiej/lepiej niż przy użyciu AbstractQueuedSynchronizer. Musiałbym sam wdrożyć oczekiwania i kolejkowanie. –

+0

@AndrewMcKinlay: Nie, sugeruję, że możesz nie potrzebować ReadWriteLock. Jeśli wszystko, czego chcesz, to przetwarzanie niektórych zadań za pomocą wielu wątków, które wykonują różne prace nad zadaniem, to moja sugestia jest jednym ze sposobów robienia tego. Ogólnie używasz ReadWriteLock, aby uzyskać wzrost wydajności, gdy jest wiele czytelników w porównaniu do pisarzy. W rzeczywistości możliwe jest umieszczenie ReadWriteLock w mojej sugerowanej implementacji (zamiast blokowania obiektu, jak opisano w mojej odpowiedzi), ale jest to optymalizacja i nie jest fundamentalna. –

+0

Potrzebuję/chcę mieć wiele czytników/pojedynczy program piszący, ponieważ obsługuje ReadWriteLock. –

1

Wiem, że już zaakceptowałeś inną odpowiedź. Ale wciąż myślę, że masz zamiar stworzyć dla siebie koszmar. W końcu klient nie wróci i zwolni te pozwolenia, a ty zaczniesz się zastanawiać, dlaczego "pisarz" nigdy nie pisze.

Gdybym to robić, zrobiłbym to tak:

Client issues a request to start a transaction 
The initial request creates a task (Runnable/Callable) and places it in an Executor for execution 
The initial request also registers that task in a Map by transaction id 

Client issues the second request to close the transaction 
The close request finds the task by transaction id in a map 
The close request calls a method on the task to indicate that it should close (probably a signal on a Condition or if data needs to be passed, placing an object in a BlockingQueue) 

Teraz zadanie transakcja musiałaby kodu:

public void run() { 
    readWriteLock.readLock().lock(); 
    try { 
     //do stuff for initializing this transaction 
     if (condition.await(someDurationAsLong, someTimeUnit)({ 
      //do the rest of the transaction stuff 
     } else { 
      //do some other stuff to back out the transaction 
     } 
    } finally { 
     readWriteLock.readLock.unlock(); 
    } 
} 
+0

Dzięki za troskę, doceniam informacje zwrotne. Twoje podejście jest interesujące. Wymagałoby to jednak wątku na transakcję, a ponieważ połączenie może zawierać wiele transakcji, jest to jeszcze więcej wątków niż wątek na połączenie. Zastanawiałem się i podjąłem kroki, aby poradzić sobie z takimi problemami - zarówno połączenia, jak i transakcje są śledzone i przekroczone, jeśli są bezczynne przez zbyt długi czas. –

+0

Oprogramowanie, nad którym pracuję, jest produkowane w 100-tych instalacjach i widziałem takie nadużycie, o które mnie ostrzegasz i jestem świadomy, że trzeba się przed nim bronić. Nie zrozumcie mnie źle, nie jestem początkującym, ale jestem też daleki od doskonałości. Jestem pewien, że niektóre z moich decyzji projektowych nie są optymalne. Jeśli jesteś zainteresowany, możesz zobaczyć więcej tego, co robię na swoim blogu - http://thesoftwarelife.blogspot.ca –

+0

Nie zrozum mnie źle, nie kwestionuję twoich umiejętności ani intelektu, myślę, że brzmi całkiem nieźle i faktycznie mówi. Jeśli jest to olbrzymi system przeznaczony do obsługi dużej liczby współbieżnych transakcji, to tak, JVM może zostać naciśnięty dla dodatkowej pamięci, aby utworzyć przestrzeń stosu dla wszystkich wątków (zakładając, że masz nieograniczoną pulę wątków). W puli powiązanej wątków twój executor ogranicza liczbę równoczesnych transakcji.Nie byłoby to oczywiście zbyt niezwykłe, ponieważ niektóre usługi przyjmują takie parametry jako parametry. Związana pula i RejectedExecutionException zatrzymuje nowe transakcje. –

0

zdają się mieć upuścił piłkę ten, wycofując com.sun.corba.se.impl.orbutil.concurrent.Mutex;

Mam na myśli to, kto przy zdrowych zmysłach myśli, że nie będziemy potrzebowali zamków nierezydencyjnych. Jesteśmy tutaj, marnując czasy, spierając się o definicję reentrant (może nieznaczna zmiana w znaczeniu w ramce btw). Tak, chcę spróbować Zaciągnąć na tym samym wątku, czy to takie złe? to nie będzie impas, ponieważ inni z niego nie będą. Nierezydencyjna blokada, która blokuje się w tym samym wątku, może być bardzo przydatna, aby zapobiec błędom w aplikacjach GUI, w których użytkownik naciska szybko i wielokrotnie na ten sam przycisk. Byłem tam, zrobiłem to, QT miało rację ... znowu.

Powiązane problemy