深入源码分析LinkedBlockingQueue

前言

        LinkedBlockingQueue是一个用链表实现的有界阻塞队列。在并发编程中,其使用的频率非常多。因为其阻塞队列的特性,所以在生产者消费者模型中,经常用来当中间件。生产者就是向队列中添加元素的线程,消费者就是从队列中取元素的线程。阻塞队列生产者用来存放元素,消费者用来获取元素的容器。简介如下:

       ·阻塞添加

         阻塞添加就是指的是当阻塞队列元素已满的时候,队列会阻塞加入元素的线程,直到元素队列不满的时候,才重新唤醒线程执行元素加入操作

        ·阻塞删除

         阻塞删除就是指的是在队列元素为空时,删除队列元素的线程将被阻塞,直到队列不再为空的时候再执行删除操作(一般都会返回被删除的元素)

常用方法简介

1.往队列中存放元素的方法

·add(E e)

·offer(E e)

·put(E e)

2.往队列中取元素的方法

·remove()

·poll()

·take()

用一个表格做一个对比,然后用源码对表格中的内容进行一个解释

方法/处理方法抛出异常返回特殊值一直阻塞
插入方法add(E e)offer(E e)put(E e)
移除方法remove()poll()take()

 

 

 

 

抛出异常:当队列为满时,如果再往队列中插入元素,则会抛出IllegalStateException("Queue full")异常。当队列为空的时候,从队列中获取元素会抛出NoSuchElementException异常

返回特殊值:当往队列中插入元素的时候,会返回元素是否插入成功;如果是移除方法,则是从中获取一个元素,没有返回null。

一直阻塞:当阻塞队列满的时候,如果生产者线程往队列里put元素,队列会一直阻塞生产者线程,直到队列可用或者相应中断退出。当队列为空的时候,如果消费者线程从队列里take元素,队列会阻塞住消费者线程,直到队列不为空。

源码分析

        首先是插入元素的三种方法的源码

public boolean add(E e) {
    if (offer(e))
        return true;
    else
        throw new IllegalStateException("Queue full");

add方法调用了offer方法,同时也解释了抛出异常的原因,下面则给出offer源码

public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final AtomicInteger count = this.count;//当前队列的元素
    if (count.get() == capacity)//count是否等于最大的容量
        return false;
    int c = -1;
    Node<E> node = new Node<E>(e);//new一个node(当前元素和指向下一个元素的结点)
    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;
}

①首先判断是否达到最大容量,如果达到最大容量返回false,这也就解释了add会抛出异常的原因

②然后上锁操作开始,如果小于容量则入队,入队后如果没有满的话,就唤醒一个线程,最后释放锁

③如果队列为空,则也是唤醒一个线程

最后是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 {
         while (count.get() == capacity) {
         notFull.await();//放数据的线程等待
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();//没有满的话,就可以加了
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

通过上面代码来看,可以看出来,插入操作如果队列满了的话,则会一直阻塞,直到不满为止。

       

        下面是获取元素的方法

public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

首先是remove方法

public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

其实这个方法和add方法其实差不多,同样去调用了别的方法,同时解释了其抛出异常的原因

下面看一下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)//c==最大容量
        signalNotFull();//上面拿出了一个,所以这里唤醒一个加元素的操作
    return x;
}

①判断如果为空的话,就返回null

②不为空,上锁,如果队列不空,获得队首元素,如果队列非空,则唤醒线程

③如果c到达了最大容量,唤醒加元素的操作

然后是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;
}

取元素如果队列为空的话,会进入一个等待的状态

总结

-add()实际调用的offer,区别是在队列满的时候,add会报异常

-offer()队列如果满了,直接入队失败

-put();在队列满的时候回进入阻塞的状态

 

-remove();直接调用pool操作,唯一区别是remove会抛出异常,而poll队列为空的时候,返回null

-pool();队列为空的时候直接返回null

-take();队列为空的时候,会进入等待的状态

 

补充

       在上面的操作中,其实也蕴含着CAS操作,以前的像ReentrantLock等类也有很多CAS操作,这里做一下解释。其实CAS全称为CompareAndSwap,即比较并交换。当更新值的时候,先用当前值和旧值进行比较,如果相同则更新,不同则返回一个错误码。其是concurrent存在的基础,CAS实现不是通过代码,而是通过CPU对其的支持,不同的CPU对CAS的实现方式不同,但是最后的效果是一样的。调用CAS的时候,通过调用java的Unsafe类对硬件级别对其进行操作,从而实现比较并交换这一操作,Unsafe类实现了不依赖native方法就可以实现硬件级别就可以实现硬件级别的原子操作。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值