Keperluan utama baris gilir menyekat adalah seperti berikut:
Asas fungsi baris gilir Ia adalah perlu untuk meletakkan data ke dalam baris gilir dan mendapatkan semula data daripada baris gilir.
Semua operasi baris gilir mestilah selaras dengan selamat.
Apabila baris gilir penuh dan data dimasukkan ke dalam baris gilir, benang perlu digantung Apabila data dalam baris gilir dikeluarkan untuk membenarkan ruang dalam baris gilir perlu digantung.
Apabila baris gilir kosong dan data diambil daripada baris gilir, utas perlu digantung Apabila benang menambah data pada baris gilir, utas yang digantung perlu dibangkitkan. .
Dalam baris gilir yang kami laksanakan, kami menggunakan tatasusunan untuk menyimpan data, jadi kami perlu menyediakan saiz awal tatasusunan dalam pembina dan tetapkan saiz tatasusunan.
Kami telah membincangkan di atas bahawa baris gilir menyekat adalah selaras dengan selamat , dan kita juga perlu bangun dan menyekat benang, supaya kita boleh memilih kunci masuk semula ReentrantLock
untuk memastikan keselamatan serentak, tetapi kita juga perlu bangun dan menyekat benang, supaya kita boleh memilih pembolehubah keadaan Condition
untuk operasi Wake-up dan blocking thread, yang akan kami gunakan dalam Condition
, terutamanya termasuk dua fungsi berikut:
signal
digunakan untuk membangkitkan thread memanggil Condition
Apabila menggunakan fungsi signal
, anda boleh membangunkan benang yang disekat oleh fungsi await
.
await
digunakan untuk menyekat utas Apabila utas memanggil fungsi Condition
await
, utas akan menyekat.
Oleh kerana barisan masuk dari satu hujung dan keluar dari hujung yang lain, barisan mesti mempunyai kepala dan ekor.
Selepas kami menambah beberapa data pada baris gilir, keadaan baris gilir mungkin seperti berikut:
Dalam gambar di atas Berdasarkan ini, kami menjalankan empat operasi dequeue, dan hasilnya adalah seperti berikut:
Dalam keadaan di atas, kami terus menambah 8 data, dan susun aturnya ialah seperti berikut:
Kita tahu bahawa apabila menambah data dalam rajah di atas, bukan sahaja ruang dalam separuh kedua tatasusunan digunakan, tetapi juga ruang yang tidak digunakan dalam separuh masa pertama boleh diteruskan, iaitu, dalam baris gilir Proses kitar semula dilaksanakan secara dalaman.
Untuk memastikan penggunaan kitaran tatasusunan, kita perlu menggunakan pembolehubah untuk merekodkan kedudukan kepala baris gilir dalam tatasusunan, pembolehubah untuk merekodkan kedudukan ekor baris gilir dalam tatasusunan, dan pembolehubah untuk merekodkan bilangan item dalam data.
Menurut analisis di atas, kita boleh tahu bahawa dalam kelas yang kita laksanakan sendiri, kita memerlukan pembolehubah ahli kelas berikut:
// 用于保护临界区的锁 private final ReentrantLock lock; // 用于唤醒取数据的时候被阻塞的线程 private final Condition notEmpty; // 用于唤醒放数据的时候被阻塞的线程 private final Condition notFull; // 用于记录从数组当中取数据的位置 也就是队列头部的位置 private int takeIndex; // 用于记录从数组当中放数据的位置 也就是队列尾部的位置 private int putIndex; // 记录队列当中有多少个数据 private int count; // 用于存放具体数据的数组 private Object[] items;
Pembina kami juga sangat mudah Perkara utama ialah menghantar parameter bersaiz tatasusunan dan memulakan serta menetapkan nilai kepada pembolehubah di atas.
@SuppressWarnings("unchecked") public MyArrayBlockingQueue(int size) { this.lock = new ReentrantLock(); this.notEmpty = lock.newCondition(); this.notFull = lock.newCondition(); // 其实可以不用初始化 类会有默认初始化 默认初始化为0 takeIndex = 0; putIndex = 0; count = 0; // 数组的长度肯定不能够小于0 if (size <= 0) throw new RuntimeException("size can not be less than 1"); items = (E[])new Object[size]; }
Ini adalah fungsi yang lebih penting Dalam fungsi ini, jika baris gilir tidak penuh, data boleh terus dimasukkan ke dalam tatasusunan, Anda perlu menggantung benang.
public void put(E x){ // put 函数可能多个线程调用 但是我们需要保证在给变量赋值的时候只能够有一个线程 // 因为如果多个线程同时进行赋值的话 那么可能后一个线程的赋值操作覆盖了前一个线程的赋值操作 // 因此这里需要上锁 lock.lock(); try { // 如果队列当中的数据个数等于数组的长度的话 说明数组已经满了 // 这个时候需要将线程挂起 while (count == items.length) notFull.await(); // 将调用 await的线程挂起 // 当数组没有满 或者在挂起之后再次唤醒的话说明数组当中有空间了 // 这个时候需要将数组入队 // 调用入队函数将数据入队 enqueue(x); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 解锁 lock.unlock(); } } // 将数据入队 private void enqueue(E x) { this.items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); // 唤醒一个被 take 函数阻塞的线程唤醒 }
Fungsi tawaran adalah sama dengan fungsi put, tetapi perbezaan dari fungsi put ialah apabila data dalam tatasusunan diisi, fungsi tawaran kembali false
bukannya disekat.
public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { // 如果数组满了 则直接返回false 而不是被阻塞 if (count == items.length) return false; else { // 如果数组没有满则直接入队 并且返回 true enqueue(e); return true; } } finally { lock.unlock(); } }
Fungsi ini mempunyai fungsi yang sama seperti dua fungsi di atas Ia juga menambah data pada baris gilir, tetapi apabila baris gilir tunggal penuh, fungsi ini akan membuang pengecualian.
public boolean add(E e) { if (offer(e)) return true; else throw new RuntimeException("Queue full"); }
Fungsi ini terutamanya mengeluarkan data daripada baris gilir, tetapi apabila baris gilir kosong, fungsi ini akan menyekat urutan yang memanggil fungsi:
public E take() throws InterruptedException { // 这个函数也是不能够并发的 否则可能不同的线程取出的是同一个位置的数据 // 进行加锁操作 lock.lock(); try { // 当 count 等于0 说明队列为空 // 需要将线程挂起等待 while (count == 0) notEmpty.await(); // 当被唤醒之后进行出队操作 return dequeue(); }finally { lock.unlock(); } } private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; // 将对应的位置设置为 null GC就可以回收了 if (++takeIndex == items.length) takeIndex = 0; count--; // 队列当中数据少一个了 // 因为出队了一个数据 可以唤醒一个被 put 函数阻塞的线程 如果这个时候没有被阻塞的线程 // 这个函数就不会起作用 也就说在这个函数调用之后被 put 函数挂起的线程也不会被唤醒 notFull.signal(); // 唤醒一个被 put 函数阻塞的线程 return x; }
kerana kami akan mencetak kelas kami dalam fungsi ujian seterusnya, dan apabila mencetak kelas ini, kami akan memanggil kaedah toString
objek untuk mendapatkan rentetan, dan akhirnya mencetak rentetan.
@Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("["); // 这里需要上锁 因为我们在打印的时候需要打印所有的数据 // 打印所有的数据就需要对数组进行遍历操作 而在进行遍历 // 操作的时候是不能进行插入和删除操作的 因为打印的是某 // 个时刻的数据 lock.lock(); try { if (count == 0) stringBuilder.append("]"); else { int cur = 0; // 对数据进行遍历 一共遍历 count 次 因为数组当中一共有 count // 个数据 while (cur != count) { // 从 takeIndex 位置开始进行遍历 因为数据是从这个位置开始的 stringBuilder.append(items[(cur + takeIndex) % items.length].toString() + ", "); cur += 1; } // 删除掉最后一次没用的 ", " stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length()); stringBuilder.append(']'); } }finally { lock.unlock(); } return stringBuilder.toString(); }
Kod untuk keseluruhan baris gilir menyekat yang kami selesaikan sendiri adalah seperti berikut:
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class MyArrayBlockingQueue<E> { // 用于保护临界区的锁 private final ReentrantLock lock; // 用于唤醒取数据的时候被阻塞的线程 private final Condition notEmpty; // 用于唤醒放数据的时候被阻塞的线程 private final Condition notFull; // 用于记录从数组当中取数据的位置 也就是队列头部的位置 private int takeIndex; // 用于记录从数组当中放数据的位置 也就是队列尾部的位置 private int putIndex; // 记录队列当中有多少个数据 private int count; // 用于存放具体数据的数组 private Object[] items; @SuppressWarnings("unchecked") public MyArrayBlockingQueue(int size) { this.lock = new ReentrantLock(); this.notEmpty = lock.newCondition(); this.notFull = lock.newCondition(); // 其实可以不用初始化 类会有默认初始化 默认初始化为0 takeIndex = 0; putIndex = 0; count = 0; if (size <= 0) throw new RuntimeException("size can not be less than 1"); items = (E[])new Object[size]; } public void put(E x){ lock.lock(); try { while (count == items.length) notFull.await(); enqueue(x); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } private void enqueue(E x) { this.items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); } private E dequeue() { final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; notFull.signal(); return x; } public boolean add(E e) { if (offer(e)) return true; else throw new RuntimeException("Queue full"); } public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { enqueue(e); return true; } } finally { lock.unlock(); } } public E poll() { final ReentrantLock lock = this.lock; lock.lock(); try { return (count == 0) ? null : dequeue(); } finally { lock.unlock(); } } public E take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); return dequeue(); }finally { lock.unlock(); } } @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("["); lock.lock(); try { if (count == 0) stringBuilder.append("]"); else { int cur = 0; while (cur != count) { stringBuilder.append(items[(cur + takeIndex) % items.length].toString()).append(", "); cur += 1; } stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length()); stringBuilder.append(']'); } }finally { lock.unlock(); } return stringBuilder.toString(); } }
Sekarang uji kod di atas:
Kami sekarang gunakan Barisan beratur menyekat model pengeluar-pengguna Tetapkan saiz baris gilir penyekat kepada 5. Benang pengeluar akan menambah data pada baris gilir Data ialah 10 nombor daripada 0 hingga 9. Benang pengguna akan menggunakan 10 kali ganda .
import java.util.concurrent.TimeUnit; public class Test { public static void main(String[] args) throws InterruptedException { MyArrayBlockingQueue<Integer> queue = new MyArrayBlockingQueue<>(5); Thread thread = new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " 往队列当中加入数据:" + i); queue.put(i); } }, "生产者"); Thread thread1 = new Thread(() -> { for (int i = 0; i < 10; i++) { try { System.out.println(Thread.currentThread().getName() + " 从队列当中取出数据:" + queue.take()); System.out.println(Thread.currentThread().getName() + " 当前队列当中的数据:" + queue); } catch (InterruptedException e) { e.printStackTrace(); } } }, "消费者"); thread.start(); TimeUnit.SECONDS.sleep(3); thread1.start(); } }
Keluaran kod di atas kelihatan seperti ini:
Pengeluar menambah data pada baris gilir: 0
Pengeluar menambah data pada baris gilir: 1
Pengeluar menambah data pada baris gilir: 2
Pengeluar menambah data pada baris gilir: 3
Pengeluar menambah data pada baris gilir: 4
Pengeluar menambah data pada baris gilir: 5
Pengguna mengeluarkan data daripada baris gilir: 0
Pengeluar menambah data pada baris gilir: 6
Data dalam baris gilir semasa pengguna: [1, 2, 3, 4, 5]
Data yang diambil oleh pengguna daripada baris gilir: 1
Data dalam baris gilir semasa pengguna: [2, 3 , 4 , 5]
Pengguna mengeluarkan data daripada baris gilir: 2
Data dalam baris gilir semasa pengguna: [3, 4, 5, 6]
Pengeluar menambah data pada baris gilir: 7
Pengguna mengeluarkan data daripada baris gilir: 3
Data dalam baris gilir semasa pengguna: [4, 5, 6, 7]
Pengguna mengeluarkan data daripada baris gilir: 4
Data gilir semasa pengguna: [5, 6, 7]
Pengguna mengeluarkan data daripada baris gilir: 5
Data dalam baris gilir semasa pengguna: [6, 7]
Pengeluar menambah data ke baris gilir: 8
Pengguna mengeluarkan data daripada baris gilir: 6
Data dalam baris gilir semasa pengguna: [7, 8]
Pengguna mengeluarkan data daripada baris gilir: 7
Data dalam baris gilir semasa pengguna: [8]
Pengguna mengeluarkan data daripada baris gilir: 8
Data dalam baris gilir semasa pengguna: []
Pengeluar menambah data pada baris gilir: 9
Pengguna mengeluarkan data daripada baris gilir :9
Data dalam baris gilir semasa pengguna: []
Daripada keputusan output di atas, kita tahu bahawa benang pengeluar telah digantung selepas mencetak 5, kerana jika ia tidak digantung, Benang pengeluar pasti boleh melengkapkan output sekali gus, kerana benang pengguna disekat selama 3 saat. Oleh kerana baris gilir menyekat penuh, dia tidak menyelesaikan output selepas mencetak nombor 5, menyebabkan benang pengeluar digantung. Sebaik sahaja pengguna mula menggunakan, ruang disediakan dalam baris gilir menyekat dan benang pengeluar boleh terus menghasilkan.
Atas ialah kandungan terperinci Cara menggunakan baris gilir menyekat tulisan tangan Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!