Java线程池队列LinkedBlockingQueue的详细原理分析-刘宇

CSDN博客地址:https://blog.csdn.net/liuyu973971883
文章来源:转载,原文地址:https://blog.csdn.net/weixin_41622183/article/details/88792358,感谢这位老哥的辛勤付出,写的非常棒,各位看完别忘了给这位老哥点个赞啊。如有侵权,请联系删除。

一、什么是LinkedBlockingQueue?

  • LinkedBlockingQueue内部由单链表实现,按 FIFO(先进先出)排序元素。
  • 链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
  • 添加元素和获取元素都使用了ReentrantLock锁,读写分离的,读写操作可以并行执行。
  • 如果不指定容量,默认为Integer.MAX_VALUE,也就是无界队列。通常建议手动设置一个大小。

二、LinkedBlockingQueue类图

在这里插入图片描述

从上图我们可以很清楚的看到 LinkedBlockingQueue 类中关系,Collection 接口我想大家都很熟悉,平时我们在开发中最常用的 List,Set 顶层也是该接口。按照 java 的语言风格,基本是不断抽象出公共接口,子接口或抽象类则在原有的基础上提供独有的方法。

三、BlockingQueue的方法

LinkedBlockingQueue 是 BlockingQueue 的实现类,那我们需要先看看 BlockingQueue 提供了哪些方法。

public interface BlockingQueue<E> extends Queue<E> {

    //将对象塞入队列,如果塞入成功返回true, 否则返回false。
    boolean add(E e);

    //将对象塞入到队列中,如果设置成功返回true, 否则返回false
    boolean offer(E e);

    //将元素塞入到队列中,如果队列中已经满了,
    //则该方法会一直阻塞,直到队列中有多余的空间。
    void put(E e) throws InterruptedException;

    //将对象塞入队列并设置时间
    //如果塞入成功返回 true, 否则返回 false.
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;

    //从队列中取对象,如果队列中没有对象,
    //线程会一直阻塞,直到队列中有对象,并且该方法取得了该对象。
    E take() throws InterruptedException;

    //在给定的时间里,从队列中获取对象,
    //时间到了直接调用普通的poll方法,为null则直接返回null。
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;

    //获取队列中剩余长度。
    int remainingCapacity();

    //从队列中移除指定的值。
    boolean remove(Object o);

    //判断队列中包含该对象。
    public boolean contains(Object o);

    //将队列中对象,全部移除,并加到传入集合中。
    int drainTo(Collection<? super E> c);

    //指定最多数量限制,将队列中对象,全部移除,并加到传入的集合中。
    int drainTo(Collection<? super E> c, int maxElements);
}

四、LinkedBlockingQueue讲解

看完上面的代码,我们基本知道了 LinkedBlockingQueue 队列有什么方法,简单说就是实现 BlockingQueue 接口的提供的方法。

1、内部使用到的锁

  • notEmpty 对象在队列从取出值时,如果队列中没有值了,那线程将会堵塞,等待有值进入队列后唤醒线程取值。
  • notFull 对象对象,如果塞值进队列时,队列已经满了,那线程将会堵塞,直到队列中值被消费,唤醒线程去塞入值。
/** 显示锁,在将对象从队列中取出时加的锁 */
private final ReentrantLock takeLock = new ReentrantLock();
/**线程间通信,从队列中取对象,如果队列为空时,就阻塞 */
private final Condition notEmpty = takeLock.newCondition();
/** 显示锁,在将对象塞入队列时加的锁 */
private final ReentrantLock putLock = new ReentrantLock();
/**线程间通信,塞对象入队列,如果队列满时时,就阻塞*/
private final Condition notFull = putLock.newCondition();

2、多种入队方法比较

入队方法是否阻塞适合队列
offer有界队列
put都可以

其实入队方法还要个 add 的,但是它还是调用 offer 的入队方法,这里就不介绍。offer 入队方式比较适合有界队列,offer 在队列满的时候,入队失败会返回 false,而 put 的方法在队列满的时候,会将线程阻塞。

3、源码解析

3.1、put方法

入队方法,该方法会阻塞

public void put(E e) throws InterruptedException {
	//对象为空时抛出异常
    if (e == null) throw new NullPointerException();
    int c = -1;
    //节点
    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();
}

3.2、offer方法

入队方法

public boolean offer(E e) {
	//对象为空时抛出异常
	if (e == null) throw new NullPointerException();
	//原子量,用于自增
	final AtomicInteger count = this.count;
	//判断队列是否已经满了,满了直接返回 false
	if (count.get() == capacity)
	    return false;
	int c = -1;
	Node<E> node = new Node<E>(e);
	//入队锁
	final ReentrantLock putLock = this.putLock;
	//显示锁
	putLock.lock();
	try {
		//判断队列是否已经满了
	    if (count.get() < capacity) {
	    //入队
	        enqueue(node);
	        //自增
	        c = count.getAndIncrement();
	        //入队后再判断队列是否已满
	        if (c + 1 < capacity)
	            notFull.signal();
	    }
	} finally {
	//解锁
	    putLock.unlock();
	}
	//唤醒消费线程
	if (c == 0)
	    signalNotEmpty();
	return c >= 0;
}

3.3、take方法

出队方法,该方法会阻塞

public E take() throws InterruptedException {
	E x;
	int c = -1;
	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;
}

3.4、poll方法

出队方法

public E poll() {
	final AtomicInteger count = this.count;
	//队列为空时,返回空
	if (count.get() == 0)
	    return null;
	E x = null;
	int c = -1;
	final ReentrantLock takeLock = this.takeLock;
	//加锁
	takeLock.lock();
	try {
		//判断队列中是否有对象
	    if (count.get() > 0) {
	    //出队操作
	        x = dequeue();
	        //自减
	        c = count.getAndDecrement();
	        //唤醒其他出队操作阻塞的线程
	        if (c > 1)
	            notEmpty.signal();
	    }
	} finally {
	//解锁
	    takeLock.unlock();
	}
	//唤醒其他队列满时阻塞的线程
	if (c == capacity)
	    signalNotFull();
	return x;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值