Ръководство за едновременни опашки в Java

1. Общ преглед

В този урок ще разгледаме някои от основните имплементации на едновременни опашки в Java. За общо въведение в опашките вижте нашето ръководство за статията за интерфейса на Java Queue .

2. Опашки

В многонишковите приложения опашките трябва да обработват множество едновременни сценарии производители-потребители. Правилният избор на едновременна опашка може да бъде от решаващо значение за постигане на добра производителност в нашите алгоритми.

Първо, ще видим някои важни разлики между блокиращата опашка и неблокиращата. След това ще разгледаме някои внедрения и най-добри практики.

2. Блокиране срещу неблокираща опашка

BlockingQueue предлага прост механизъм, безопасен за нишките . В тази опашка нишките трябва да изчакат наличността на опашката. Производителите ще изчакат наличния капацитет, преди да добавят елементи, докато потребителите ще изчакат, докато опашката се изпразни. В тези случаи неблокиращата опашка ще хвърли изключение или ще върне специална стойност, като null или false .

За да се постигне този блокиращ механизъм, интерфейсът BlockingQueue излага две функции върху нормалните функции на Queue : put и take . Тези функции са еквивалент на добавяне и премахване в стандартна опашка .

3. Едновременни изпълнения на опашката

3.1. ArrayBlockingQueue

Както подсказва името му, тази опашка използва масив вътрешно. В резултат на това това е ограничена опашка, което означава, че има фиксиран размер .

Една проста работна опашка е пример за случай на употреба. Този сценарий често е ниско съотношение между производител и потребител, където разделяме отнемащите време задачи между множество работници. Тъй като тази опашка не може да нараства безкрайно, ограничението на размера действа като праг за безопасност, ако паметта е проблем .

Говорейки за паметта, важно е да се отбележи, че опашката предварително разпределя масива. Макар че това може да подобри пропускателната способност, може да отнеме и повече памет, отколкото е необходимо . Например опашката с голям капацитет може да остане празна за дълги периоди от време.

Също така, ArrayBlockingQueue използва едно заключване както за операции за пускане, така и за вземане . Това гарантира, че няма презаписване на записи, с цената на изпълнението.

3.2. LinkedBlockingQueue

В LinkedBlockingQueue използва LinkedList вариант, където всяка опашка елемент е нова възлова точка. Въпреки че това прави опашката неограничена по принцип, тя все още има твърд лимит от Integer.MAX_VALUE .

От друга страна, можем да зададем размера на опашката, като използваме конструктора LinkedBlockingQueue (int capacity) .

Тази опашка използва отделни брави за операции за пускане и приемане . В резултат на това и двете операции могат да се извършват паралелно и да се подобри производителността.

Тъй като LinkedBlockingQueue може да бъде или ограничен, или неограничен, защо да използваме ArrayBlockingQueue над този? LinkedBlockingQueue трябва да разпределя и освобождава възли всеки път, когато елемент се добавя или премахва от опашката . Поради тази причина ArrayBlockingQueue може да бъде по-добра алтернатива, ако опашката расте бързо и бързо се свива.

Изпълнението на LinkedBlockingQueue се казва, че е непредсказуемо. С други думи, винаги трябва да профилираме нашите сценарии, за да гарантираме, че използваме правилната структура на данните.

3.3. PriorityBlockingQueue

В PriorityBlockingQueue е нашата зелена до решение , когато трябва да консумират продукти в определен ред . За да постигне това, PriorityBlockingQueue използва двоична купчина, базирана на масив.

Докато вътрешно използва единен заключващ механизъм, операцията за вземане може да се осъществи едновременно с операцията за пускане . Използването на обикновен фиксатор прави това възможно.

Типичен случай на използване е консумирането на задачи с различни приоритети. Не искаме задача с нисък приоритет да замени тази с висок приоритет .

3.4. DelayQueue

Използваме DelayQueue, когато потребителят може да вземе само изтекъл артикул . Интересното е, че той използва PriorityQueue вътрешно, за да поръча артикулите до изтичането им.

Тъй като това не е опашка с общо предназначение, тя не обхваща толкова много сценарии, колкото ArrayBlockingQueue или LinkedBlockingQueue . Например, можем да използваме тази опашка, за да реализираме обикновен цикъл на събития, подобен на този, който се намира в NodeJS. Поставяме асинхронни задачи в опашката за по-нататъшна обработка, когато изтекат.

3.5. LinkedTransferQueue

В LinkedTransferQueue въвежда трансфер метод. Докато други опашки обикновено блокират при производство или консумиране на елементи, LinkedTransferQueue позволява на производителя да изчака консумацията на елемент .

Използваме LinkedTransferQueue, когато се нуждаем от гаранция, че определен елемент, който поставяме в опашката, е взет от някой. Също така можем да приложим прост алгоритъм за обратно налягане, използвайки тази опашка. Всъщност, като блокират производителите до потреблението, потребителите могат да управляват потока от произведени съобщения .

3.6. SynchronousQueue

Докато опашките обикновено съдържат много елементи, SynchronousQueue винаги ще има най-много един елемент. С други думи, трябва да видим SynchronousQueue като прост начин за обмен на някои данни между две нишки .

Когато имаме две нишки, които се нуждаят от достъп до споделено състояние, често ги синхронизираме с CountDownLatch или други механизми за синхронизация. Използвайки SynchronousQueue , можем да избегнем тази ръчна синхронизация на нишки .

3.7. ConcurrentLinkedQueue

В ConcurrentLinkedQueue е единственият без блокиране на опашката на това ръководство. Следователно, той предоставя алгоритъм „без изчакване“, при който добавянето и анкетирането е гарантирано безопасно за нишки и се връща незабавно . Вместо заключвания, тази опашка използва CAS (Compare-And-Swap).

Вътрешно се основава на алгоритъм от прости, бързи и практични неблокиращи и блокиращи едновременни алгоритми на опашката от Maged M. Michael и Michael L. Scott.

Той е идеален кандидат за съвременни реактивни системи , където използването на блокиращи структури от данни често е забранено.

От друга страна, ако нашият потребител в крайна сметка чака в цикъл, вероятно трябва да изберем блокираща опашка като по-добра алтернатива.

4. Заключение

В това ръководство разгледахме различни едновременни внедрения на опашки, обсъждайки техните силни и слаби страни. Имайки това предвид, ние сме по-добре подготвени да разработваме ефективни, трайни и налични системи.