Rumah  >  Artikel  >  Java  >  Bagaimana untuk menguasai Java LinkedBlockingQueue

Bagaimana untuk menguasai Java LinkedBlockingQueue

王林
王林ke hadapan
2023-04-18 16:04:031080semak imbas

    Beratur boleh dilihat di mana-mana dalam kehidupan Anda perlu beratur untuk membayar bil hospital, beratur untuk melakukan ujian asid nukleik, beratur untuk menunggu lampu isyarat di dalam kereta. , dsb.

    Bagaimana untuk menguasai Java LinkedBlockingQueue

    Baris gilir ialah struktur data di mana yang pertama didahulukan didahulukan, dan yang kedua didahulukan didahulukan. Dilaksanakan menggunakan tatasusunan dan senarai terpaut. Biasanya digunakan untuk menyelaraskan pelaksanaan tugas dan pertukaran data.

    Pengenalan

    LinkedBlockingQueue ialah baris gilir sekatan bersempadan bermakna baris gilir mempunyai kapasiti maksimum sekatan bermakna jika baris gilir penuh, anda mahu terus menambah pada elemen , maka operasi ini akan digantung, dan operasi penambahan tidak akan diteruskan sehingga terdapat ruang dalam baris gilir. Jika baris gilir sudah kosong dan anda ingin mendapatkan elemen daripada baris gilir, operasi akan digantung sehingga terdapat elemen dalam baris gilir untuk meneruskan operasi pemerolehan.

    Prinsip Pelaksanaan

    LinkedBlockingQueue secara dalaman menggunakan senarai terpaut sebagai struktur storan elemen. Dua kunci digunakan secara dalaman, masing-masing untuk operasi penyimpanan dan operasi mendapatkan semula.

    Apabila melakukan operasi akses, kunci mesti diperoleh terlebih dahulu sebelum operasi akses boleh dilakukan, memastikan LinkedBlockingQueue selamat untuk benang.

    LinkedBlockingQueue melepasi dua baris gilir syarat Syarat, satu keadaan tidak Penuh dan satu keadaan tidak kosong. Apabila memasukkan elemen ke dalam baris gilir, jika diadili bahawa baris gilir semasa penuh, utas akan disekat melalui keadaan notFull sehingga utas lain memberitahu utas bahawa baris gilir boleh terus memasukkan elemen. Apabila mengalih keluar elemen daripada baris gilir, jika ditentukan bahawa baris gilir semasa kosong, utas akan disekat melalui keadaan notEmpty sehingga utas lain boleh terus mendapatkan elemen melalui utas ini.

    Ini memastikan tiada ralat dalam operasi capaian urutan. Elakkan membuang elemen yang dimasukkan apabila baris gilir penuh; juga elakkan mendapat nilai nol apabila baris gilir kosong.

    Pembina

    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
    
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

    Dalam pembina tanpa parameter, Integer.MAX_VALUE digunakan secara lalai sebagai kapasiti maksimum baris gilir.

    Dalam pembina berparameter, anda boleh menentukan sendiri kapasiti maksimum baris gilir dan mencipta nod kepala dan nod ekor. Kemudian LinkedBlockingQueue menggunakan menuju senarai terpaut sehala.

    private final int capacity;
    
    /** Current number of elements */
    private final AtomicInteger count = new AtomicInteger();
    
    transient Node<E> head;
    
    private transient Node<E> last;
    
    // 取锁
    private final ReentrantLock takeLock = new ReentrantLock();
    
    private final Condition notEmpty = takeLock.newCondition();
    
    // 存锁
    private final ReentrantLock putLock = new ReentrantLock();
    
    private final Condition notFull = putLock.newCondition();

    Dan apabila objek dimulakan, dua kunci dicipta, yang digunakan untuk operasi penyimpanan dan operasi mendapatkan semula. Dua baris gilir bersyarat dibuat, masing-masing untuk keadaan baris gilir kosong dan penuh.

    Fungsi sisip

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        final int c;
        final Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)
            signalNotEmpty();
    }

    1 Dapatkan kunci

    2. Tentukan sama ada baris gilir semasa sudah penuh

    • Jika. baris gilir Jika ia penuh, panggil kaedah await() baris gilir bersyarat notFull untuk menyekat benang dan menggantung operasi sisipan benang. Elakkan masalah limpahan dalaman.

    • Jika ia tidak penuh, terus panggil enqueue function enqueue untuk memasukkannya ke penghujung baris gilir.

    3 Periksa sama ada baris gilir penuh pada masa ini

    Jika tidak penuh, panggil kaedah isyarat() bagi baris gilir keadaan notFull untuk membangunkan baris gilir disekat dalam urutan baris gilir keadaan notFull.

    4. Buka Kunci

    • Semak sama ada bilangan elemen baris gilir sebelum memasukkan elemen adalah sama dengan 0

    • adalah sama kepada 0, kemudian panggil notEmpty Kaedah isyarat() baris gilir bersyarat memberitahu baris gilirnya bahawa ia tidak kosong sekarang, dan boleh membangunkan benang yang disekat untuk mendapatkan elemen.

    Bagaimana untuk menguasai Java LinkedBlockingQueue

    Mengapa anda perlu memanggil kaedah isyarat() bagi baris gilir keadaan notFull? Oleh kerana kunci yang digunakan oleh operasi ambil baris gilir dan operasi simpan adalah berbeza, ini bermakna apabila satu utas melakukan operasi simpan, utas lain boleh melakukan operasi ambil. Mari lihat contoh berikut:

    Bagaimana untuk menguasai Java LinkedBlockingQueue

    • Jumlah kapasiti baris gilir ialah 5, dan bilangan elemen semasa ialah 5. Benang A memperoleh kunci dan ingin memasukkan elemen. Tetapi kerana kapasiti baris gilir penuh, kunci dilepaskan dan ditambah pada baris gilir syarat, menunggu untuk dibangkitkan.

    • Benang B memperoleh kunci dan mahu memasukkan elemen. Tetapi kerana kapasiti baris gilir penuh, kunci dilepaskan dan ditambah pada baris gilir syarat, menunggu untuk dibangkitkan.

    • Benang C memperoleh kunci dan mengeluarkan elemen 1. Dan bangunkan benang A yang disekat dalam baris gilir keadaan melalui kaedah isyarat notFull. Thread A menyertai baris gilir penyegerakan selepas dikejutkan, tetapi masih belum bersaing untuk mengunci pada masa ini.

    • Benang D memperoleh kunci dan mengeluarkan elemen 2. Tetapi kod yang membangkitkan benang yang disekat masih belum dilaksanakan.

    • Benang A bersaing untuk mendapatkan kunci dan mula memasukkan elemen. Selepas memasukkan elemen, ia diperiksa bahawa bilangan elemen baris gilir ialah 4, iaitu kurang daripada jumlah kapasiti baris gilir, jadi kaedah isyarat notFull dilaksanakan untuk membangkitkan benang B yang disekat dalam baris gilir bersyarat. Selepas benang B dibangkitkan, ia menyertai baris gilir penyegerakan dan mula bersaing untuk kunci.

    • Benang B bersaing untuk mendapatkan kunci dan mula memasukkan elemen. Selepas memasukkan elemen, ia diperiksa bahawa bilangan elemen baris gilir adalah sama dengan 5, dan tiada operasi bangun dilakukan.

    这样做的目的是尽快的唤醒阻塞线程,可以更快的完成插入元素操作。因为线程存和取的操作相互之间并不是互斥的,而是独立运行的,提高吞吐量。

    获取函数

    public E take() throws InterruptedException {
        final E x;
        final int c;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

    1.获得取锁

    2.判断当前队列是否为空

    • 如果队列没有元素,调用 notEmpty 条件队列的 await() 方法,将该线程阻塞,暂停该线程的获取操作。避免获取元素出错。

    • 如果不为空,则直接调用出队函数 dequeue 移除队列第一个元素,并返回给客户端。

    3.检查此时队列是否为空

    如果不为空,则调用 notEmpty 条件队列的 signal() 方法,唤醒被阻塞在 notEmpty 条件队列的线程。

    4.释放锁

    5.检查获取元素前的队列元素数量是否等于最大容量

    等于最大容量,因为此时已经取出一个元素,因此队列处于未满的状态,可以唤醒阻塞在 notFull 条件的线程,让线程继续插入元素。

    Bagaimana untuk menguasai Java LinkedBlockingQueue

    步骤 3 的目的是尽快的唤醒阻塞线程,可以更快的完成取元素操作。提高吞吐量。可以尝试自己画出流程图。

    入队函数

    private void enqueue(Node<E> node) {
        last = last.next = node;
    }

    入队函数极其简单,只要将最后一个元素的 next 指针指向当前元素即完成了插入操作。

    Bagaimana untuk menguasai Java LinkedBlockingQueue

    出队函数

    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }

    我们前面说 LinkedBlockingQueue 使用的是有头链表。头节点只是作为一个标志,实际上并不是一个真正的元素。当获取元素时,将头节点的下一个节点作为头节点,将原来的头节点取消引用,被垃圾回收即可。

    Bagaimana untuk menguasai Java LinkedBlockingQueue

    应用场景

    适用场景

    LinkedBlockingQueue 和 ArrayBlockingQueue 一样适用于多个线程之间需要共享数据、协调任务执行的场景。因此可以总结出以下几个应用场景:

    线程池:线程池是一个常见的并发编程模型,它通过线程池中的线程执行任务。并且可以重复使用这些线程。在线程池中,可以使用 LinkedBlockingQueue 来存储需要执行的任务,以此控制任务数量和执行顺序。当线程池中的线程执行完任务之后,可以从 LinkedBlockingQueue 中取出下一个任务执行。

    生产者-消费者:在生产者-消费者模型中,生产者负责生产数据,消费者负责对数据进行处理。在这种模式下,LinkedBlockingQueue 可以作为生产者与消费者之间的数据通道,保证线程安全和数据正确。

    实际应用场景

    • Nacos: Nacos 是一个动态服务发现、配置和服务管理平台,它使用 LinkedBlockingQueue 来实现内部的任务队列。

    • Tomcat:从 Tomcat 7 开始,请求队列默认使用了 LinkedBlockingQueue 实现。

    • Hystrix: 一个流行的容错框架,其默认使用 LinkedBlockingQueue 作为请求队列。

    Atas ialah kandungan terperinci Bagaimana untuk menguasai Java LinkedBlockingQueue. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam