Vodič za java.util.concurrent.BlockingQueue

1. Pregled

U ovom ćemo članku pogledati jedan od najkorisnijih konstrukata java.util.concurrent za rješavanje istodobnog problema proizvođač-potrošač. Pogledat ćemo API BlockingQueue sučelje i kako metode s tog sučelja olakšavaju pisanje istodobnih programa.

Dalje u članku pokazat ćemo primjer jednostavnog programa koji ima više niti proizvođača i više niti potrošača.

2. BlockingQueue Vrste

Možemo razlikovati dvije vrste BlockingQueue:

  • neograničeni red - može rasti gotovo u nedogled
  • ograničeni red - s definiranim maksimalnim kapacitetom

2.1. Neograničeni red čekanja

Stvaranje neograničenih redova je jednostavno:

BlockingQueue blockingQueue = novo LinkedBlockingDeque ();

Kapacitet blockingQueue bit će postavljeno na Cijeli broj.MAX_VALUE. Sve operacije koje dodaju element u neograničeni red nikada neće blokirati, pa bi moglo narasti na vrlo veliku veličinu.

Najvažnija stvar kod dizajniranja programa proizvođač-potrošač koji koristi neograničeni BlockingQueue je da potrošači trebaju biti u mogućnosti potrošiti poruke onoliko brzo koliko proizvođači dodaju poruke u red. Inače, memorija bi se mogla napuniti i dobili bismo Bez memorije iznimka.

2.2. Ograničeni red čekanja

Druga vrsta redova je ograničeni red. Takve redove možemo stvoriti prosljeđivanjem kapaciteta kao argumenta konstruktoru:

BlockingQueue blockingQueue = novi LinkedBlockingDeque (10);

Ovdje imamo blockingQueue koji ima kapacitet jednak 10. To znači da kada proizvođač pokušava dodati element u već puni red, ovisno o metodi koja je korištena za njegovo dodavanje (ponuda(), dodati() ili staviti()), blokirat će se dok ne postane dostupan prostor za umetanje predmeta. Inače, operacije neće uspjeti.

Korištenje ograničenog reda dobar je način za dizajniranje istodobnih programa, jer kada umetnemo element u već puni red, te operacije moraju pričekati dok potrošači ne sustignu i učine dio prostora dostupnim u redu. Omogućuje nam gašenje bez ikakvog napora s naše strane.

3. BlockingQueue API

Postoje dvije vrste metoda u BlockingQueue sučeljemetode odgovorne za dodavanje elemenata u red i metode koje dohvaćaju te elemente. Svaka metoda iz te dvije skupine ponaša se drugačije u slučaju da je red pun / prazan.

3.1. Dodavanje elemenata

  • dodati() - vraća se pravi ako je umetanje bilo uspješno, u suprotnom baca znak IllegalStateException
  • put () - ubacuje navedeni element u red čekanja na slobodno mjesto ako je potrebno
  • ponuda () - vraća se pravi ako je umetanje bilo uspješno, u suprotnom lažno
  • ponuda (E e, dugo čekanje, jedinica vremenske jedinice) - pokušava umetnuti element u red i čeka dostupno mjesto unutar određenog vremenskog ograničenja

3.2. Dohvaćanje elemenata

  • uzeti() - čeka glavni element u redu i uklanja ga. Ako je red prazan, blokira se i čeka da element postane dostupan
  • anketa (dugo čekanje, jedinica vremenske jedinice) - dohvaća i uklanja glavu reda, čekajući do određenog vremena čekanja ako je potrebno da element postane dostupan. Povrat null nakon isteka vremena

Ove su metode najvažniji građevni blokovi iz BlockingQueue sučelje prilikom izgradnje programa proizvođač-potrošač.

4. Primjer višenitnog proizvođača i potrošača

Stvorimo program koji se sastoji od dva dijela - proizvođača i potrošača.

Proizvođač će proizvesti nasumični broj od 0 do 100 i stavit će taj broj u BlockingQueue. Imat ćemo 4 proizvodne niti i koristit ćemo staviti() metodu za blokiranje dok u redu za čekanje nema dovoljno mjesta.

Važno je upamtiti da moramo zaustaviti potrošačke niti da čekaju da se element neograničeno pojavi u redu čekanja.

Dobra tehnika kojom se proizvođaču signalizira da više nema poruka za obradu jest slanje posebne poruke koja se naziva otrovna tableta. Moramo poslati onoliko tableta s otrovima koliko imamo potrošača. Zatim, kada potrošač uzme tu posebnu poruku o otrovnim tabletama iz reda, izvršenje će završiti elegantno.

Pogledajmo klasu proizvođača:

javna klasa NumbersProducer implementira Runnable {private BlockingQueue numbersQueue; privatni konačni int toxicPill; privatni konačni int toxicPillPerProducer; javni NumbersProducer (BlockingQueue numbersQueue, int toxicPill, int otroPillPerProducer) {this.numbersQueue = numbersQueue; this.poisonPill = otrovaPill; this.poisonPillPerProducer = otrovaPillPerProducer; } public void run () {try {generirajbrojeve (); } catch (InterruptedException e) {Thread.currentThread (). interrupt (); }} private void generirajNumbers () baca InterruptedException {for (int i = 0; i <100; i ++) {numbersQueue.put (ThreadLocalRandom.current (). nextInt (100)); } za (int j = 0; j <otrojPillPerProducer; j ++) {numbersQueue.put (otrojPill); }}}

Naš konstruktor proizvođača uzima kao argument BlockingQueue koja se koristi za koordinaciju obrade između proizvođača i potrošača. Mi vidimo tu metodu generiraj brojeve () stavit će 100 elemenata u red čekanja. Potrebna je i poruka o otrovnim pilulama da biste znali koju vrstu poruke morate staviti u red kada će izvršenje biti završeno. Tu poruku treba staviti otrovPillPerProducer puta u red.

Svaki će potrošač uzeti element iz a BlockingQueue koristeći uzeti() metoda, tako da će blokirati sve dok u redu ne bude elementa. Nakon uzimanja Cijeli broj iz reda provjerava je li poruka otrovna tableta, ako da, izvršenje niti je završeno. U suprotnom, ispisat će rezultat na standardnom izlazu zajedno s trenutnim imenom niti.

To će nam dati uvid u unutarnji rad naših potrošača:

javna klasa NumbersConsumer implementira Runnable {private BlockingQueue queue; privatni konačni int toxicPill; javni NumbersConsumer (BlockingQueue queue, int toxicPill) {this.queue = queue; this.poisonPill = otrovaPill; } public void run () {try {while (true) {Integer number = queue.take (); if (number.equals (toxicPill)) {return; } System.out.println (Thread.currentThread (). GetName () + "rezultat:" + broj); }} catch (InterruptedException e) {Thread.currentThread (). interrupt (); }}}

Važno je primijetiti upotrebu reda čekanja. Isto kao u konstruktoru proizvođača, red se predaje kao argument. To možemo jer BlockingQueue mogu se dijeliti između niti bez ikakve izričite sinkronizacije.

Sad kad imamo svog proizvođača i potrošača, možemo započeti naš program. Moramo definirati kapacitet reda i postavili smo ga na 100 elemenata.

Želimo imati 4 proizvodne niti, a broj potrošačkih niti bit će jednak broju dostupnih procesora:

int OBVEZAN = 10; int N_PRODUCERS = 4; int N_CONSUMERS = Runtime.getRuntime (). availableProcessors (); int toxicPill = Integer.MAX_VALUE; int toxicPillPerProducer = N_POTROŠAČI / N_PROIZVODA; int mod = N_POTROŠAČI% N_PROIZVODA; Red čekanja BlockingQueue = novi LinkedBlockingQueue (OBVEZAN); for (int i = 1; i <N_PRODUCERS; i ++) {new Thread (new NumbersProducer (red čekanja, toxicPill, toxicPillPerProducer)). start (); } for (int j = 0; j <N_CONSUMERS; j ++) {new Thread (new NumbersConsumer (queue, toxicPill)). start (); } nova nit (novi NumbersProducer (red čekanja, toxicPill, toxicPillPerProducer + mod)). start (); 

BlockingQueue je stvoren pomoću konstrukcije s kapacitetom. Stvaramo 4 proizvođača i N potrošača. Našu poruku o otrovnim pilulama navodimo kao Cijeli broj.MAX_VALUE jer takvu vrijednost naš proizvođač nikada neće poslati pod normalnim radnim uvjetima. Ovdje je najvažnije primijetiti to BlockingQueue koristi se za koordinaciju rada između njih.

Kad pokrenemo program, 4 proizvodne niti stavljat će nasumično Cijeli brojevi u BlockingQueue a potrošači će te elemente uzimati iz reda. Svaka nit ispisat će na standardni izlaz ime niti zajedno s rezultatom.

5. Zaključak

Ovaj članak prikazuje praktičnu upotrebu BlockingQueue i objašnjava metode koje se koriste za dodavanje i dohvaćanje elemenata iz njega. Također, pokazali smo kako se koristi višenitni program proizvođač-potrošač BlockingQueue za koordinaciju rada između proizvođača i potrošača.

Provedbu svih ovih primjera i isječaka koda možete pronaći u projektu GitHub - ovo je projekt zasnovan na Mavenu, pa bi ga trebalo lako uvesti i pokrenuti kakav jest.