2008-12-11 13 views
16

Myślę, że przeformułuję moje pytanie zGdzie należy używać implementacji BlockingQueue zamiast prostych implementacji kolejki?

Gdzie należy używać implementacji BlockingQueue zamiast prostych implementacji kolejki?

do

czym korzyści/wady BlockingQueue powyżej implementacji kolejek biorąc pod uwagę aspekty, takie jak prędkość, współbieżności lub inne cechy, które różnią się np czas na dostęp do ostatniego elementu.

Użyłem obu rodzajów Kolejki. Wiem, że kolejka blokująca jest zwykle używana w aplikacji współbieżnej. Pisałem prostą pulę ByteBuffer, w której potrzebowałem jakiegoś symbolu zastępczego dla obiektów ByteBuffer. Potrzebowałem najszybszej implementacji kolejki wątkowej. Nawet istnieją implementacje List, jak ArrayList, który ma stały czas dostępu dla elementów.

Czy ktokolwiek może dyskutować o zaletach i wadach BlockingQueue vs Queue vs List implementations?

Obecnie używam ArrayList do przechowywania tych obiektów ByteBuffer.

Którą strukturę danych należy użyć do przechowywania tych obiektów?

Odpowiedz

26

Ograniczona pojemność BlockingQueue jest również przydatna, jeśli chcesz zmniejszyć przepustowość jakiegoś żądania. Dzięki nieograniczonej kolejce producenci mogą znacznie wyprzedzić konsumentów. Zadania zostaną ostatecznie wykonane (chyba że jest ich tyle, że powodują one OutOfMemoryError), ale producent już dawno mógł się poddać, więc wysiłek jest marnowany.

W takich sytuacjach lepiej zasygnalizować potencjalnemu producentowi, że kolejka jest pełna, i szybko zrezygnować z błędu. Na przykład producent może być żądaniem sieciowym, z użytkownikiem, który nie chce czekać zbyt długo, i nawet jeśli nie będzie zużywał wielu cykli procesora podczas oczekiwania, zużywa ograniczone zasoby, takie jak gniazdo i pamięć . Rezygnacja sprawi, że zadania, które zostały w kolejce, będą miały większą szansę na zakończenie w odpowiednim czasie.


Odnośnie zmienionego pytania, które interpretuję jako "Jaki jest dobry zbiór do przechowywania obiektów w basenie?"

Bez ograniczeń LinkedBlockingQueue to dobry wybór dla wielu basenów. Jednak w zależności od strategii zarządzania pulą może również działać ConcurrentLinkedQueue.

W aplikacji do łączenia blokowanie "wstaw" nie jest właściwe. Kontrolowanie maksymalnego rozmiaru kolejki jest zadaniem menedżera puli, który decyduje, kiedy tworzyć lub niszczyć zasoby puli. Klienci puli pożyczają i zwracają zasoby z puli. Dodanie nowego obiektu lub zwrócenie wcześniej pożyczonego obiektu do puli powinno być szybkimi, nieblokującymi operacjami. Zatem kolejka o ograniczonej pojemności nie jest dobrym wyborem dla pul.

Z drugiej strony podczas pobierania obiektu z puli większość aplikacji chce poczekać, aż zasób będzie dostępny. Operacja "take", która blokuje, przynajmniej tymczasowo, jest znacznie bardziej wydajna niż "ciągłe oczekiwanie", które trwa do momentu, aż zasób będzie dostępny. W tym przypadku dobrym wyborem jest LinkedBlockingQueue. Kredytobiorca może blokować w nieskończoność z take lub ograniczyć czas, który chce zablokować z poll.

Rzadziej spotykany przypadek, gdy klient nie chce blokować w ogóle, ale ma możliwość utworzenia zasobu dla siebie, jeśli pula jest pusta. W takim przypadku dobrym wyborem jest ConcurrentLinkedQueue. Jest to rodzaj szarego obszaru, w którym byłoby miło dzielić się zasobem (na przykład pamięcią) w jak największym stopniu, ale szybkość jest jeszcze ważniejsza. W gorszym przypadku degeneruje się to do każdej nitki mającej własną instancję zasobu; wtedy byłoby bardziej wydajnie, aby nie zawracać sobie głowy dzieleniem się między wątkami.

Obie te kolekcje zapewniają dobrą wydajność i łatwość użycia w aplikacji współbieżnej. W przypadku aplikacji innych niż współbieżne, trudno jest pokonać numer ArrayList. Nawet w przypadku kolekcji, które dynamicznie rosną, koszty narzutu w przeliczeniu na elementy w postaci LinkedList pozwalają na ArrayList z niektórymi pustymi gniazdami, aby zachować konkurencyjność w pamięci.

+1

Thanks Erickson za taki miły wyjaśnienia. To rozwiązało mój problem. –

2

Zobaczysz BlockingQueue w sytuacjach wielowątkowych. Na przykład musisz podać parametr BlockingQueue jako parametr, aby utworzyć ThreadPoolExecutor, jeśli chcesz go utworzyć za pomocą konstruktora. W zależności od rodzaju kolejki, którą przekazujesz, executor może działać inaczej.

0

BlockingQueue jest również przydatny do serializacji operacji równoległych, które zależą od siebie.

Aby przynieść betonu (choć nieco arbitralne) przykład here jest równoległy test w czasie rzeczywistym kolejki pacjent aplikacji internetowych, gdzie callInPatient() musi uruchomić równolegle z registerPatientToAppointment(), ale musi czekać aż registerPatientToAppointment() została zakończona przed wykonaniem callPatientInAtDoctorsOffice() :

public class ClinicPatientQueueAppTest extends ParallelTest { 

    private static final BlockingQueue WAIT_FOR_REGISTRATION_QUEUE = new ArrayBlockingQueue(2); 

    @Test 
    public void callInPatient() { 
     loginToDoctorsOffice("user", "password"); 
     waitUntilRegistrationCompleted(); // <-- 
     callPatientInAtDoctorsOffice(); 
    } 

    @Test 
    public void registerPatientToAppointment() { 
     registerPatientAtRegistrationKiosk("Patient Peter"); 
     notifyRegistrationCompleted(); // <-- 
    } 

    private void waitUntilRegistrationCompleted() { 
     WAIT_FOR_REGISTRATION_QUEUE.take(); 
    } 

    private void notifyRegistrationCompleted() { 
     WAIT_FOR_REGISTRATION_QUEUE.put(this); 
    } 

} 
0

jest to realizacja Queue który dodatkowo wspiera operacje

czekać na kolejkę do się niepusty podczas pobierania elementu,

i

czekać na przestrzeń będzie dostępna w kolejce podczas przechowywania element .

Jeśli wymagane powyżej funkcjonalności nastąpi implementacji Queue następnie użyć Blocking Queue

Powiązane problemy