ArrayBlockingQueue源码分析

基于jdk1.8源码进行分析的。

之后有三四篇博文都是针对BlockingQueue接口的实现类进行源码分析。阻塞队列在我的博文分类-多线程里面也有一些相关方面的分析介绍,这里是基于具体实现类的源码分析。下面开始正题。

首先看一下BlockingQueue接口源码。

public interface BlockingQueue<E> extends Queue<E> { 
    //添加元素
    boolean add(E e);
    //添加元素
    boolean offer(E e);
    //添加元素
    void put(E e) throws InterruptedException;
    //添加元素
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;
    //取出元素
    E take() throws InterruptedException;
    //取出元素
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;
    //返回的是不阻塞的元素的数量 
    int remainingCapacity();
    //取出元素
    boolean remove(Object o);
    //批量获取元素
    int drainTo(Collection<? super E> c);
    //批量获取元素
    int drainTo(Collection<? super E> c, int maxElements);
}

整体接口定义上我们看到类的继承结构,该接口继承了Queue接口,我们都知道Queue是Collection接口下面的一个子接口,或者说Queue是继承自Collection接口,Collection接口我们都很熟悉,因为我们常用的List,Set都是继承自它。

BlockingQueue--阻塞队列,不仅支持队列操作,而且当出现下面情况的时候,他也能处理:

  1. 当获取元素时,如果队列为空,则一直等待直到队列非空。
  2. 当存储元素时,如果队列中没有空间进行存储,则一直等待直到有空间进行存储。

所以这也正好体现了为什么叫做阻塞队列。

当然在还有方法element和peek用于检查队列中的元素,也是队列操作常见的接口,熟悉数据结构的话,应该对此操作并不陌生。下面我们先看第一个实现类:ArrayBlockingQueue的源码分析。

类继承结构

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable 

 可以看到该类继承了AbstractQueue类,实现了BlockingQueue接口和Serializable接口。

成员属性

//用来存储元素的数组
final Object[] items; 
//下一次执行task,poll,peek或者remove的索引
int takeIndex;
//下一次执行put,offer,或者add的索引
int putIndex;
//当前数组中存放的元素数量
int count;
//提供的lock锁
final ReentrantLock lock;
//队列空了,就挂起,等到有元素放进去后唤醒
private final Condition notEmpty;
//队列满了就挂起,等到取出一个元素后唤醒
private final Condition notFull;

构造方法

ArrayBlockingQueue(int capacity)

    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

可以看到调用了另外一个构造方法

 ArrayBlockingQueue(int capacity, boolean fair)

    public ArrayBlockingQueue(int capacity, boolean fair) {
        //数据校验,不符合,抛出异常
        if (capacity <= 0)
            throw new IllegalArgumentException();
        //final Object[] items;
        //Object数组
        this.items = new Object[capacity];
        //lock锁,每次创建该队列,就会有一个lock锁
        lock = new ReentrantLock(fair);
        //两个额外的对应的挂起条件
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }

ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c) 

通过集合进行批量初始化。该构造方法也是通过调用上面的构造方法实现的。

     public ArrayBlockingQueue(int capacity, boolean fair,
                              Collection<? extends E> c) {
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            int i = 0;
            try {
                for (E e : c) {
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            count = i;
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

我们源码看到这里可以简单总结一下:

  1. 该类是通过数组实现的,因为有Object数组
  2. 该类动态维护了两个索引,putIndex和takeIndex两个索引
  3. 该类没新创建一个实例就会有创建一个新的ReentrantLock锁,和Condition
  4. 初始化的时候可以看到,可以让用户自定义一个布尔值,在构造方法里面我们可以看到是用来创建非公平锁还是公平锁

我们下面继续。

核心方法

put(E e)

    public void put(E e) throws InterruptedException {
        //数据合法性校验
        checkNotNull(e);
        //获取锁
        final ReentrantLock lock = this.lock;
        //后面分析
        lock.lockInterruptibly();
        try {
            //队列满了
            while (count == items.length)
                //等待
                notFull.await();
            //否则入队--后面分析
            enqueue(e);
        } finally {
            //释放锁
            lock.unlock();
        }
    }

里面调用的到了checkNotNull,源码如下:

checkNotNull(Object v)

    private static void checkNotNull(Object v) {
        //进行数据校验,如果为空,直接抛出异常
        if (v == null)
            throw new NullPointerException();
    }

lockInterruptibly() 

立即响应中断。

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

 enqueue(E x) 

入队操作

    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        //记录当前数组
        final Object[] items = this.items;
        //数组添加元素
        items[putIndex] = x;
        //数据校验,如果满了,putIndex置为0
        if (++putIndex == items.length)
            putIndex = 0;
        //元素数维护
        count++;
        //能添加了,唤醒不空的线程
        notEmpty.signal();
    }

看到这里博主不得不进行一个DEMO演示。

package demo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 锁中断测试
 * lock方法会忽略中断请求,继续获取锁直到成功;而lockInterruptibly则直接抛出中断异常来立即响应中断,由上层调用者处理中断
 * lock()
 * 		适用于锁获取操作不受中断影响的情况,此时可以忽略中断请求正常执行加锁操作,因为该操作仅仅记录了中断状态
 * 		(通过Thread.currentThread().interrupt()操作,只是恢复了中断状态为true,并没有对中断进行响应)。
 * 如果要求被中断线程不能参与锁的竞争操作,则此时应该使用lockInterruptibly方法,
 * 		一旦检测到中断请求,立即返回不再参与锁的竞争并且取消锁获取操作(即finally中的cancelAcquire操作)
 * @Description: TODO
 * @author       BurgessLee
 * @date         2019年5月5日  
 *
 */
public class ReentrantLockLockInterruptibly {

	public static void main(String[] args) {
		ReentrantLock lock = new ReentrantLock();
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() +"-starting....");
					lock.lockInterruptibly();//获取锁
					if(lock.isLocked()) {
						System.out.println(Thread.currentThread().getName() +"-is locked....");
					}
					System.out.println(Thread.currentThread().getName() +"-start to sleep....");
					Thread.sleep(10000);
					System.out.println(Thread.currentThread().getName() +"-end to sleep....");
					System.out.println(Thread.currentThread().getName() +"-interrupt:"+ Thread.currentThread().isInterrupted());
					System.out.println(Thread.currentThread().getName() +"-ending....");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					if(lock.isLocked()) {
						System.out.println(Thread.currentThread().getName() +"-finally is locked....");
						lock.unlock();
					}
				}
			}
		},"Thread0");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e2) {
			e2.printStackTrace();
		}
		t1.start();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e2) {
			e2.printStackTrace();
		}
		t1.interrupt();
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					System.out.println(Thread.currentThread().getName() +"-starting....");
					lock.lock();
					if(lock.isLocked()) {
						System.out.println(Thread.currentThread().getName() +"-is locked....");
					}
					System.out.println(Thread.currentThread().getName() +"-start to sleep....");
					Thread.sleep(10000);
					System.out.println(Thread.currentThread().getName() +"-end to sleep....");
					System.out.println(Thread.currentThread().getName() +"-interrupt:"+ Thread.currentThread().isInterrupted());
					System.out.println(Thread.currentThread().getName() +"-ending....");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}finally {
					if(lock.isLocked()) {
						System.out.println(Thread.currentThread().getName() +"-finally is locked....");
						lock.unlock();
					}
				}
			}
		},"Thread1");
		t2.start();//启动线程
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		t2.interrupt();
	}	
	
}

这里进一步总结了关于ReentrantLock的lock方法和lockInterruptibly方法的区别,在于对于线程中断处理的问题上,具体可以看代码演示。下面我们继续源码分析。看完了put方法,我也很好奇其他的添加元素的方法是怎么实现的。下面我们就一起来学习下。

 offer(E e)

    public boolean offer(E e) {
        //数据合法性校验
        checkNotNull(e);
        //获取锁
        final ReentrantLock lock = this.lock;
        //上锁
        lock.lock();
        try {
            //如果满了,返回false
            if (count == items.length)
                return false;
            else {
                //没有满,添加元素,返回true
                enqueue(e);
                return true;
            }
        } finally {
            //释放锁
            lock.unlock();
        }
    }

可以看到就是一个正常队列的操作。

add(E e)

调用父类方法实现的。

    public boolean add(E e) {
        return super.add(e);
    }

好了,下面我们一起来看一下关于take源码的实现。

take()

    public E take() throws InterruptedException {
        //获取锁
        final ReentrantLock lock = this.lock;
        //立即中断,如果没有中断,继续操作
        lock.lockInterruptibly();
        try {
            //如果队列为空
            while (count == 0)
                //等待
                notEmpty.await();
            //否则入队操作
            return dequeue();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

同样take元素也是调用了对应的出队的方法,下面我们看一下。

dequeue()

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        //记录当前元素数组
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        //根据动态维护的takeIndex来获取指定元素
        E x = (E) items[takeIndex];
        //释放当前元素的
        items[takeIndex] = null;
        //数据合法性校验
        if (++takeIndex == items.length)
            takeIndex = 0;
        //元素数量维护
        count--;
        //执行出队操作
        if (itrs != null)
            itrs.elementDequeued();
        //唤醒对应的线程
        notFull.signal();
        //返回出队元素
        return x;
    }

该方法用来执行出队操作。上面代码中注释内容已经很清楚,此处不再一一赘述。

下面我们来看一个DEMO演示。

package demo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * ArrayBlockingQueue测试例程
 * @ClassName:   ArrayBlockingQueueDemo  
 * @Description: TODO
 * @author       BurgessLee
 * @date         2019年5月5日  
 *
 */
public class ArrayBlockingQueueDemo {
	
	public static void main(String[] args) {
		//1.创建队列
		ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(3);
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() +"....start....");
				for(int i = 0; i < 10; i++) {
					try {
//						TimeUnit.SECONDS.sleep(1);
						queue.put(i);
						System.out.println(Thread.currentThread().getName() +"存入的元素是:" + i);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		},"thread0").start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() +"....start....");
				for(int i = 0; i < 10; i++) {
					try {
//						TimeUnit.SECONDS.sleep(1);
						Integer take = queue.take();
						System.out.println(Thread.currentThread().getName() +"获取到的元素是:" + take);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		},"thread1").start();	
	}
	
}

打印输出结果是:

thread0....start....
thread0存入的元素是:0
thread0存入的元素是:1
thread0存入的元素是:2
thread1....start....
thread0存入的元素是:3
thread1获取到的元素是:0
thread1获取到的元素是:1
thread0存入的元素是:4
thread1获取到的元素是:2
thread0存入的元素是:5
thread1获取到的元素是:3
thread0存入的元素是:6
thread1获取到的元素是:4
thread0存入的元素是:7
thread1获取到的元素是:5
thread0存入的元素是:8
thread1获取到的元素是:6
thread0存入的元素是:9
thread1获取到的元素是:7
thread1获取到的元素是:8
thread1获取到的元素是:9

以上就是针对ArrayBlockingQueue的源码分析的全部过程了。如果有不对的地方还请指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值