java基础——LinkedBlockingQueue源码学习

 

目录

类图记录

 方法说明

 核心源码解析

总结


  • 类图记录

  •  方法说明

核心方法功能描述
方法定义功能
private void signalNotEmpty()队列不为空,唤醒一个take线程
private void signalNotFull() 队列不满,唤醒一个put线程
private void enqueue(Node<E> node)入队:队列尾部新增节点
private E dequeue()出队:移除第一个节点
void fullyLock()take、put加锁
void fullyUnlock() take、put锁释放
public void put(E e)在链表尾部插入新增的元素,如果空间不足则等待
public boolean offer(E e, long timeout, TimeUnit unit)指定等待时间内完成节点put返回true,超过等待时间未true,返回false
public boolean offer(E e) 如果有足够的空间插入元素成功返回true,否则返回false
public E take()从第一个节点取出元素,如队列为空则wait等待通知
public E poll(long timeout, TimeUnit unit)指定时间内从队列中取元素,超时未获取到元素返回null
public E poll()从队列中poll第一个节点数据,如果队列为空,返回null,不等待
public E peek()探索第一个节点元素返回,不存在返回null,只读取不移除
void unlink(Node<E> p, Node<E> trail)取消链接,原来trail节点的next是p,执行该方法取消链接关系
public boolean remove(Object o)遍历移除指定的元素,成功移除一个即返回true,不存在则返回false
public boolean contains(Object o)判断一个元素在队列中是否存在
public Object[] toArray()将队列中的元素转object数组返回
public <T> T[] toArray(T[] a)将队列中的元素转制定类型数组返回
public void clear()清除队列中所有元素,保留头尾节点
public int drainTo(Collection<? super E> c, int maxElements)将队列中的元素取出并写入到传入的集合中,返回成功写入集合的元素个数
private void writeObject(java.io.ObjectOutputStream s)队列元素save到流
private void readObject(java.io.ObjectInputStream s) 从流重建此队列

 

 

  •  核心源码解析

1)入队

 private void enqueue(Node<E> node) {
        // assert putLock.isHeldByCurrentThread();
        // assert last.next == null;
	//新增节点赋值给last.next,并将这个节点赋值为尾节点,
        last = last.next = node;
    }

 调用入队方法前加锁,判断容量是否已满,已满则等待,未满可以继续插入,入队代码解析如下图。

入队示意图
入队示意图

2)出队

 /**
     * 移除节点
     * Removes a node from head of queue.
     *
     * @return the node
     */
    private E dequeue() {
        // assert takeLock.isHeldByCurrentThread();
        // assert head.item == null;
	//取得头结点
        Node<E> h = head;
	//取得第一个节点
        Node<E> first = h.next;
	//头结点的next指向自己,原头结点脱了链表,下次gc回收
        h.next = h; // help GC
	//第一个节点赋值到首节点
        head = first;
	//获取第一个节点的item,用于后面返回本次移除的第一个节点item
        E x = first.item;
	//第一个节点 item赋值为null,即首节点item赋值为null
        first.item = null;
        return x;
    }

出队的操作是从head节点,找到first节点,然后把head节点next指向自己达到脱离链表的作用。再把原来的first节点内容取出删除,设置为新的head节点。

出队示意图
出队示意图

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();
	    //节点数加1并返回去数据之前的元素个数
            c = count.getAndDecrement();//执行减1操作,并返回操作之前的值
	    //判断队列中仍然有值,唤醒一个等待take数据的线程
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
	//如果take该元素之前,容量已满,则可能存在等待唤醒的put线程,唤醒之
        if (c == capacity)
            signalNotFull();
        return x;
    }

4)put元素

在链表尾部插入新增的元素,如果空间不足则等待

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;
	//写入数据加锁
        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.
             */
	    //容量已满时,等待
            while (count.get() == capacity) {
                notFull.await();
            }
            enqueue(node);
	    //获取增加这个节点之前的容量大小,并将count+1
            c = count.getAndIncrement();
	    //判断增加节点后如果未满则唤醒其他put线程
            if (c + 1 < capacity)
                notFull.signal();
        } finally {
            putLock.unlock();
        }
	//如果普通该元素之前,元素个数为0,则可能存在等待唤醒的take线程,唤醒之
        if (c == 0)
            signalNotEmpty();
    }
  • 总结

  1. LinkedBlockingQueue使用单链表存储队列元素,提供了单链表相关的取、放、排放(drainTo)、转换输出等功能;
  2. 取、放数据使用ReentrantLock以及Condition来实现,对取放操作加锁,使用条件队列等待、通知来管理线程操作;
  3. 使用LinkedBlockingQueue尽量制定容量大小,否则维护的链表容量太大(Integer的最大值大小),而不会使用到阻塞特性,当然容量已满除外;
  4. 由于使用了单链表,取、放数据操作较快,GC压力比较大,而元素探索和包含查询等操作的速度相比数组存储结构花费的时间更长。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值