JUC阻塞队列BlockingQueue---LinkedBlockingQueue

什么是阻塞队列?

LinkedBlockingQueue

LinkedBlockingQueue是一个基于链表实现的阻塞队列,该阻塞队列的大小默认为Integer.MAX_VALUE,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限(随着元素的添加,队列的大小会动态增加,如果剩余内存不足,会出现OOM)。为了避免队列过大造成机器负载或者内存爆满的情况出现,在使用的时候建议手动传一个队列的大小

使用

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class BlockingQueueExample {

    private static final int QUEUE_CAPACITY = 5;
    private static final int PRODUCER_DELAY_MS = 1000;
    private static final int CONSUMER_DELAY_MS = 2000;

    public static void main(String[] args) throws InterruptedException {
        // 创建一个容量为QUEUE_CAPACITY的阻塞队列
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(QUEUE_CAPACITY);
        // 创建一个无界的阻塞队列
        BlockingQueue<String> queueMax = new LinkedBlockingQueue<>();

        // 创建一个生产者线程
        new Thread(() -> {
            while (true) {
                try {
                    // 在队列满时阻塞
                    queue.put("producer");
                    System.out.println("生产了一个元素,队列中元素个数:" + queue.size());
                    Thread.sleep(PRODUCER_DELAY_MS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // 创建一个消费者线程
        new Thread(() -> {
            while (true) {
                try {
                    // 在队列为空时阻塞
                    String element = queue.take();
                    System.out.println("消费了一个元素,队列中元素个数:" + queue.size());
                    Thread.sleep(CONSUMER_DELAY_MS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

原理

LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行。

链表结构

    /**
     * Linked list node class
     */
    static class Node<E> {
        E item;

        /**
         * One of:
         * - the real successor Node
         * - this Node, meaning the successor is head.next
         * - null, meaning there is no successor (this is the last node)
         */
        Node<E> next;

        Node(E x) { item = x; }
    }

构造方法

可以看到,当不知道队列大小时,则默认采用Integer.MAX_VALUE作为队列的大小。

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}.
     */
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with the given (fixed) capacity.
     *
     * @param capacity the capacity of this queue
     * @throws IllegalArgumentException if {@code capacity} is not greater
     *         than zero
     */
    public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        // 初始化head和last指针为空值节点
        last = head = new Node<E>(null);
    }

    /**
     * Creates a {@code LinkedBlockingQueue} with a capacity of
     * {@link Integer#MAX_VALUE}, initially containing the elements of the
     * given collection,
     * added in traversal order of the collection's iterator.
     *
     * @param c the collection of elements to initially contain
     * @throws NullPointerException if the specified collection or any
     *         of its elements are null
     */
    public LinkedBlockingQueue(Collection<? extends E> c) {
        this(Integer.MAX_VALUE);
        final ReentrantLock putLock = this.putLock;
        putLock.lock(); // Never contended, but necessary for visibility
        try {
            int n = 0;
            for (E e : c) {
                if (e == null)
                    throw new NullPointerException();
                if (n == capacity)
                    throw new IllegalStateException("Queue full");
                enqueue(new Node<E>(e));
                ++n;
            }
            count.set(n);
        } finally {
            putLock.unlock();
        }
    }

内部常量

    /** The capacity bound, or Integer.MAX_VALUE if none */
    // 队列容量
    private final int capacity;

    /** Current number of elements */
    // 元素数量
    private final AtomicInteger count = new AtomicInteger();

    /**
     * Head of linked list.
     * Invariant: head.item == null
     */
     // 链表头  本身是不存储任何元素的,初始化时item指向null
    transient Node<E> head;

    /**
     * Tail of linked list.
     * Invariant: last.next == null
     */
     // 链表尾
    private transient Node<E> last;

    /** Lock held by take, poll, etc */
    // take锁   锁分离,提高效率
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    // notEmpty条件
	// 当队列无元素时,take锁会阻塞在notEmpty条件上,等待其它线程唤醒
    private final Condition notEmpty = takeLock.newCondition();

    /** Lock held by put, offer, etc */
    // put锁
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    // notFull条件
	// 当队列满了时,put锁会会阻塞在notFull上,等待其它线程唤醒
    private final Condition notFull = putLock.newCondition();

入队put方法

    /**
     * Inserts the specified element at the tail of this queue, waiting if
     * necessary for space to become available.
     *
     * @throws InterruptedException {@inheritDoc}
     * @throws NullPointerException {@inheritDoc}
     */
    public void put(E e) throws InterruptedException {
    	// 入队元素为空时,抛出空指针异常
        if (e == null) throw new NullPointerException();
        // Note: convention in all put/take/etc is to preset local var
        // holding count negative to indicate failure unless set.
        int c = -1;
        // 新建一个节点
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        // 使用put锁进行加锁
        putLock.lockInterruptibly();
        try {
            /*
             * Note that count is used in wait guard even though it is
             * not protected by lock. This works because count can
             * only decrease at this point (all other puts are shut
             * out by lock), and we (or some other waiting put) are
             * signalled if it ever changes from capacity. Similarly
             * for all other uses of count in other wait guards.
             */
            // 如果当前队列数量和队列最大值相等,则进行notFull等待(阻塞生产者线程)
            while (count.get() == capacity) {
                notFull.await();
            }
            // 队列不满时,入队
            enqueue(node);
            // 获取当前队列长度,同时将当前队列长度+1
            c = count.getAndIncrement();
            
            // 如果现队列长度小于容量,notFull条件队列转同步队列,准备唤醒一个阻塞在notFull条件上的线程(可以继续入队) 
        	// 这里为啥要唤醒一下呢?
        	// 因为可能有很多线程阻塞在notFull这个条件上,而取元素时只有取之前队列是满的才会唤醒notFull,此处不用等到取元素时才唤醒 
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
        	// put锁解锁,唤醒其余生产者线程入队
            putLock.unlock();
        }
        // 如果原队列长度为0,;立即唤醒阻塞在notEmpty上的线程
        if (c == 0)
            signalNotEmpty();
    }

    /**
     * Links node at end of queue.
     *
     * @param node the node
     */
    private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
        // 直接加到last后面,last指向入队元素
        last = last.next = node;
    }

    /**
     * Signals a waiting take. Called only from put/offer (which do not
     * otherwise ordinarily lock takeLock.)
     */
    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        // takeLock加锁
        takeLock.lock();
        try {
        	// notEmpty条件队列转同步队列,准备唤醒阻塞在notEmpty上的线程
            notEmpty.signal();
        } finally {
			// takeLock解锁,唤醒消费者线程
            takeLock.unlock();
        }
    }

出队take方法

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        // takeLock加锁
        takeLock.lockInterruptibly();
        try {
        	// 队列空的时候,则进行notEmpty等待(消费者线程阻塞)
            while (count.get() == 0) {
                notEmpty.await();
            }
            // 获取元素,出队
            x = dequeue();
            // 获取原队列长度,同时原队列长度-1
            c = count.getAndDecrement();
            if (c > 1)
            	// 如果原队列长度大于1,则唤醒其余消费者线程
                notEmpty.signal();
        } finally {
        	// takeLock解锁
            takeLock.unlock();
        }
        // 为什么队列是满的才唤醒阻塞在notFull上的线程呢?
    	// 因为唤醒是需要加putLock的,这是为了减少锁的次数,所以,这里索性在放完元素就检测一下,未满就唤醒其它notFull上的线程,
    	// 这也是锁分离带来的代价
    	// 如果取之前队列长度等于容量(已满),则唤醒阻塞在notFull的线程
        if (c == capacity)
            signalNotFull();
        return x;
    }

    /**
     * Removes a node from head of queue.
     *
     * @return the node
     */
    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
         // head节点本身是不存储任何元素的
    	// 这里把head删除,并把head下一个节点作为新的值
    	// 并把其值置空,返回原来的值
        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;
    }

    /**
     * Signals a waiting put. Called only from take/poll.
     */
    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
	        // notFull条件队列转同步队列,准备唤醒阻塞在notFull上的线程
            notFull.signal();
        } finally {
        	// putLock解锁,真正的唤醒生产者线程
            putLock.unlock();
        }
    }

help GC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值