Condivisione della tecnologia

[Sistema operativo] Coda di blocco e modello produttore-consumatore

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

coda di blocco

1. Concetto

La coda di blocco è un tipo speciale di coda e segue anch'essa il principio "first in, first out".

La coda di blocco può essere una struttura dati thread-safe e presenta le seguenti caratteristiche:

  • Quando la coda è piena, continuare ad entrare nella coda verrà bloccato finché altri thread non prenderanno elementi dalla coda..
  • Quando la coda è vuota, anche il proseguimento della rimozione dalla coda si bloccherà finché altri thread non inseriranno elementi nella coda.

Uno scenario applicativo tipico del blocco delle code è il "modello produttore-consumatore". Si tratta di un modello di sviluppo molto tipico
produttore

Il modello del consumo risolve il forte problema di accoppiamento tra produttori e consumatori attraverso un contenitore.

Produttori e consumatori non comunicano direttamente tra loro, ma comunicano tramite code di blocco. Pertanto, dopo che il produttore ha prodotto i dati, non ha bisogno di attendere che il consumatore li elabori, ma li getta direttamente nella coda di blocco non richiede dati al produttore, ma viene prelevato direttamente dalla coda di blocco.

Ci sono due fasi principali nell’implementazione del modello produttore-consumatore:

  1. implementare il produttore
  2. realizzare il consumatore

2. Coda di blocco nella libreria standard

Esiste una coda di blocco incorporata nella libreria standard Java Se dobbiamo utilizzare le code di blocco in alcuni programmi, possiamo utilizzare direttamente la coda di blocco nella libreria standard.
Potere.

  • BlockingQueue è un'interfaccia. La vera classe di implementazione è LinkedBlockingQueue
  • Il metodo put viene utilizzato per bloccare l'ingresso nella coda e il metodo take viene utilizzato per bloccare l'uscita dalla coda.
  • BlockingQueue dispone anche di metodi come offer, poll e peek, ma questi metodi non hanno caratteristiche di blocco.

Lo pseudo codice per bloccare la coda è il seguente:

BlockingQueue<String> queue = new LinkedBlockingQueue<>();

// ⼊队列
queue.put("abc");

// 出队列. 如果没有 put 直接 take, 就会阻塞.
String elem = queue.take();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3. Modello produttore-consumatore

public static void main(String[] args) throws InterruptedException {

	BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>();
	
	Thread customer = new Thread(() -> {
		while (true) {
			try {
				int value = blockingQueue.take();
				System.out.println("消费元素: " + value);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}, "消费者");
	
customer.start();

	Thread producer = new Thread(() -> {
		Random random = new Random();
		while (true) {
			try {
				int num = random.nextInt(1000);
				System.out.println("生产元素: " + num);
				blockingQueue.put(num);
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}, "生产者");
	
	producer.start();
	customer.join();
	producer.join();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

4. Blocco dell'implementazione della coda

  1. Ciò si ottiene attraverso il metodo della "coda circolare".
  2. Utilizzare sincronizzato per il controllo del blocco.
  3. Quando put inserisce elementi, determina se la coda è piena e attende (nota che l'attesa deve essere eseguita in un ciclo. Quando viene risvegliata, la coda potrebbe non essere piena perché più thread possono essere risvegliati contemporaneamente. ).
  4. Quando take estrae l'elemento, determina se la coda è vuota e attende (è anche un'attesa ciclica).
public class BlockingQueue {

	private int[] items = new int[1000];
	private volatile int size = 0;
	private volatile int head = 0;
	private volatile int tail = 0;
	
	public void put(int value) throws InterruptedException {
		synchronized (this) {
		// 此处最好使⽤ while.
		// 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
		// 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能⼜已经队列满了
		// 就只能继续等待
			while (size == items.length) {
				wait();
			}
			items[tail] = value;
			tail = (tail + 1) % items.length;
			size++;
			notifyAll();
		}
	}
	
	public int take() throws InterruptedException {
		int ret = 0;
		synchronized (this) {
			while (size == 0) {
				wait();
			}
			ret = items[head];
			head = (head + 1) % items.length;
			size--;
			notifyAll();
		}
		return ret;
	}
	
	public synchronized int size() {
		return size;
	}
	
	// 测试代码
	public static void main(String[] args) throws InterruptedException {
		BlockingQueue blockingQueue = new BlockingQueue();
		Thread customer = new Thread(() -> {
		while (true) {
			try {
				int value = blockingQueue.take();
				System.out.println(value);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}, "消费者");	
	
		customer.start();
		Thread producer = new Thread(() -> {
			Random random = new Random();
				while (true) {					try {
						blockingQueue.put(random.nextInt(10000));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "生产者");
			
		producer.start();
		customer.join();
		producer.join();
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71

Riassumere

  1. La coda di blocco equivale a un buffer, che bilancia le capacità di elaborazione di produttori e consumatori (ritaglio e riempimento dei picchi).
  2. Il blocco delle code può anche disaccoppiare produttori e consumatori.